Import Cobalt 6.16000 Change-Id: If1f4d0d72e803dd65b8405737d248c3a7ccad77b
diff --git a/src/base/atomicops.h b/src/base/atomicops.h index 230a601..6d04858 100644 --- a/src/base/atomicops.h +++ b/src/base/atomicops.h
@@ -31,6 +31,10 @@ #include "base/basictypes.h" #include "build/build_config.h" +#if defined(OS_STARBOARD) +#include "starboard/atomic.h" +#endif // defined(OS_STARBOARD) + #if (defined(OS_WIN) && defined(ARCH_CPU_64_BITS)) || defined(__LB_XB360__) || defined(__LB_XB1__) // windows.h #defines this (only on x64). This causes problems because the // public API also uses MemoryBarrier at the public name for this fence. So, on @@ -43,6 +47,12 @@ namespace base { namespace subtle { +#if defined(OS_STARBOARD) +typedef SbAtomic32 Atomic32; +#if defined(ARCH_CPU_64_BITS) +typedef SbAtomic64 Atomic64; +#endif // defined(ARCH_CPU_64_BITS) +#else typedef int32 Atomic32; #ifdef ARCH_CPU_64_BITS // We need to be able to go between Atomic64 and AtomicWord implicitly. This @@ -55,6 +65,7 @@ typedef intptr_t Atomic64; #endif #endif +#endif // defined(OS_STARBOARD) // Use AtomicWord for a machine-sized pointer. It will use the Atomic32 or // Atomic64 routines below, depending on your architecture.
diff --git a/src/base/base.gypi b/src/base/base.gypi index fde1a96..c1a5fff 100644 --- a/src/base/base.gypi +++ b/src/base/base.gypi
@@ -589,11 +589,6 @@ ['include', 'sys_string_conversions_linux.cc'], ], }], - [ 'OS=="lb_shell" and target_arch!="linux" and target_arch!="android"', { - 'sources!': [ - 'string16.cc', # wchar_t is 2-bytes wide, string16 == wstring here. - ], - }], [ 'OS=="lb_shell" and target_arch=="android"', { 'sources/' : [ ['exclude', 'message_pump_shell.cc'], @@ -660,14 +655,6 @@ # We use thread_checker_impl_atomic.cc instead. 'threading/thread_checker_impl.cc', ], - 'conditions': [ - ['target_os!="linux" and target_arch!="android"', { - 'sources!': [ - # Since wchar_t is 2-bytes wide, string16 == wstring here. - 'string16.cc', - ], - }], - ], }], # OS == "starboard" [ 'OS=="lb_shell" or OS=="starboard"', { 'sources!': [
diff --git a/src/base/callback_helpers.h b/src/base/callback_helpers.h index 52cb71b..ada0090 100644 --- a/src/base/callback_helpers.h +++ b/src/base/callback_helpers.h
@@ -25,6 +25,27 @@ return ret; } +inline bool ResetAndRunIfNotNull(base::Closure* cb) { + if (cb->is_null()) { + return false; + } + base::Closure ret(*cb); + cb->Reset(); + ret.Run(); + return true; +} + +template <typename Sig, typename ParamType> +bool ResetAndRunIfNotNull(base::Callback<Sig>* cb, const ParamType& param) { + if (cb->is_null()) { + return false; + } + base::Callback<Sig> ret(*cb); + cb->Reset(); + ret.Run(param); + return true; +} + } // namespace base #endif // BASE_CALLBACK_HELPERS_H_
diff --git a/src/base/circular_buffer_shell.cc b/src/base/circular_buffer_shell.cc index ae58e4b..ce64f42 100644 --- a/src/base/circular_buffer_shell.cc +++ b/src/base/circular_buffer_shell.cc
@@ -15,7 +15,7 @@ #include "starboard/memory.h" #define malloc SbMemoryAllocate #define realloc SbMemoryReallocate -#define free SbMemoryFree +#define free SbMemoryDeallocate #endif static inline void* add_to_pointer(void* pointer, size_t amount) {
diff --git a/src/base/debug/trace_event_impl.cc b/src/base/debug/trace_event_impl.cc index 5af61e5..ac641c3 100644 --- a/src/base/debug/trace_event_impl.cc +++ b/src/base/debug/trace_event_impl.cc
@@ -114,6 +114,7 @@ TraceEvent::TraceEvent(int thread_id, TimeTicks timestamp, + TimeTicks thread_timestamp, char phase, const unsigned char* category_enabled, const char* name, @@ -124,6 +125,7 @@ const unsigned long long* arg_values, unsigned char flags) : timestamp_(timestamp), + thread_timestamp_(thread_timestamp), id_(id), category_enabled_(category_enabled), name_(name), @@ -242,6 +244,7 @@ void TraceEvent::AppendAsJSON(std::string* out) const { int64 time_int64 = timestamp_.ToInternalValue(); + int64 thread_time_int64 = thread_timestamp_.ToInternalValue(); int process_id = TraceLog::GetInstance()->process_id(); // Category name checked at category creation time. DCHECK(!strchr(name_, '"')); @@ -266,6 +269,11 @@ } *out += "}"; + // add thread timestamp only if it was available + if (TimeTicks::HasThreadNow()) { + StringAppendF(out, ",\"tts\":%" PRId64, thread_time_int64); + } + // If id_ is set, print it out as a hex string so we don't loose any // bits (it might be a 64-bit pointer). if (flags_ & TRACE_EVENT_FLAG_HAS_ID) @@ -667,6 +675,7 @@ #endif TimeTicks now = TimeTicks::NowFromSystemTraceTime() - time_offset_; + TimeTicks thread_now = TimeTicks::ThreadNow(); NotificationHelper notifier(this); { AutoLock lock(lock_); @@ -710,7 +719,7 @@ logged_events_.push_back( TraceEvent(thread_id, - now, phase, category_enabled, name, id, + now, thread_now, phase, category_enabled, name, id, num_args, arg_names, arg_types, arg_values, flags)); @@ -795,7 +804,7 @@ trace_event_internal::SetTraceValue(it->second, &arg_type, &arg_value); logged_events_.push_back( TraceEvent(it->first, - TimeTicks(), TRACE_EVENT_PHASE_METADATA, + TimeTicks(), TimeTicks(), TRACE_EVENT_PHASE_METADATA, &g_category_enabled[g_category_metadata], "thread_name", trace_event_internal::kNoEventId, num_args, &arg_name, &arg_type, &arg_value,
diff --git a/src/base/debug/trace_event_impl.h b/src/base/debug/trace_event_impl.h index b8b4110..6d373c7 100644 --- a/src/base/debug/trace_event_impl.h +++ b/src/base/debug/trace_event_impl.h
@@ -64,6 +64,7 @@ TraceEvent(); TraceEvent(int thread_id, TimeTicks timestamp, + TimeTicks thread_timestamp, char phase, const unsigned char* category_enabled, const char* name, @@ -87,6 +88,7 @@ std::string* out); TimeTicks timestamp() const { return timestamp_; } + TimeTicks thread_timestamp() const { return thread_timestamp_; } // Exposed for unittesting: @@ -107,6 +109,7 @@ private: // Note: these are ordered by size (largest first) for optimal packing. TimeTicks timestamp_; + TimeTicks thread_timestamp_; // id_ can be used to store phase-specific data. unsigned long long id_; TraceValue arg_values_[kTraceMaxNumArgs];
diff --git a/src/base/file_util_proxy_unittest.cc b/src/base/file_util_proxy_unittest.cc index e238b32..dcd5ad8 100644 --- a/src/base/file_util_proxy_unittest.cc +++ b/src/base/file_util_proxy_unittest.cc
@@ -39,10 +39,18 @@ } void DidFinish(PlatformFileError error) { - error_ = error; + if (error_ == PLATFORM_FILE_OK) { + error_ = error; + } MessageLoop::current()->Quit(); } + void NeedsMoreWork(PlatformFileError error) { + if (error_ == PLATFORM_FILE_OK) { + error_ = error; + } + } + void DidCreateOrOpen(PlatformFileError error, PassPlatformFile file, bool created) { @@ -376,10 +384,14 @@ ASSERT_EQ(10, info.size); // Run. + PlatformFile file = GetTestPlatformFile(PLATFORM_FILE_OPEN | + PLATFORM_FILE_WRITE); FileUtilProxy::Truncate( + file_task_runner(), file, 7, + Bind(&FileUtilProxyTest::NeedsMoreWork, weak_factory_.GetWeakPtr())); + FileUtilProxy::Flush( file_task_runner(), - GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE), - 7, + file, Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr())); MessageLoop::current()->Run(); @@ -409,10 +421,14 @@ ASSERT_EQ(10, info.size); // Run. + PlatformFile file = GetTestPlatformFile(PLATFORM_FILE_OPEN | + PLATFORM_FILE_WRITE); FileUtilProxy::Truncate( + file_task_runner(), file, 53, + Bind(&FileUtilProxyTest::NeedsMoreWork, weak_factory_.GetWeakPtr())); + FileUtilProxy::Flush( file_task_runner(), - GetTestPlatformFile(PLATFORM_FILE_OPEN | PLATFORM_FILE_WRITE), - 53, + file, Bind(&FileUtilProxyTest::DidFinish, weak_factory_.GetWeakPtr())); MessageLoop::current()->Run();
diff --git a/src/base/file_util_starboard.cc b/src/base/file_util_starboard.cc index 0c6511a..a2e9b2b 100644 --- a/src/base/file_util_starboard.cc +++ b/src/base/file_util_starboard.cc
@@ -193,8 +193,8 @@ } base::PlatformFile destination_file = base::CreatePlatformFileUnsafe( - to_path, base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE, NULL, - NULL); + to_path, base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE, + NULL, NULL); if (destination_file == base::kInvalidPlatformFileValue) { DPLOG(ERROR) << "CopyFile(): Unable to open destination file: " << to_path.value();
diff --git a/src/base/file_util_unittest.cc b/src/base/file_util_unittest.cc index 6f4e111..81fdcc7 100644 --- a/src/base/file_util_unittest.cc +++ b/src/base/file_util_unittest.cc
@@ -211,7 +211,16 @@ // Simple function to dump some text into a new file. void CreateTextFile(const FilePath& filename, const std::wstring& contents) { -#if defined(__LB_PS3__) +#if defined(OS_STARBOARD) + SbFile file = + SbFileOpen(filename.value().c_str(), kSbFileCreateAlways | kSbFileWrite, + NULL /*out_created*/, NULL /*out_error*/); + EXPECT_TRUE(file != kSbFileInvalid); + std::string utf8 = WideToUTF8(contents); + int bytes_written = SbFileWrite(file, utf8.c_str(), utf8.length()); + EXPECT_TRUE(bytes_written == utf8.length()); + SbFileClose(file); +#elif defined(__LB_PS3__) FILE *file = fopen(filename.value().c_str(), "w"); ASSERT_TRUE(file != NULL); fputws(contents.c_str(), file); @@ -232,18 +241,35 @@ ASSERT_TRUE(file.is_open()); file << contents; file.close(); -#endif +#endif // defined(OS_STARBOARD) } // Simple function to take out some text from a file. std::wstring ReadTextFile(const FilePath& filename) { +#if defined(OS_STARBOARD) + SbFile file = + SbFileOpen(filename.value().c_str(), kSbFileOpenOnly | kSbFileRead, + NULL /*out_created*/, NULL /*out_error*/); + EXPECT_TRUE(file != kSbFileInvalid); + char utf8_buffer[64] = {0}; + int bytes_read = + SbFileRead(file, utf8_buffer, SB_ARRAY_SIZE_INT(utf8_buffer)); + EXPECT_TRUE(bytes_read >= 0); + SbFileClose(file); + std::wstring result; + bool did_convert = + UTF8ToWide(utf8_buffer, SbStringGetLength(utf8_buffer), &result); + EXPECT_TRUE(did_convert); + return result; +#elif defined(__LB_PS3__) wchar_t contents[64]; -#if defined(__LB_PS3__) FILE *file = fopen(filename.value().c_str(), "r"); EXPECT_TRUE(file != NULL); fgetws(contents, arraysize(contents), file); fclose(file); + return std::wstring(contents); #elif defined(__LB_WIIU__) + wchar_t contents[64]; char mb_sequence_buffer[64]; int fd = open(filename.value().c_str(), O_RDONLY); EXPECT_TRUE(fd >= 0); @@ -253,14 +279,16 @@ const char * mb_sequence = mb_sequence_buffer; size_t nbytes = mbsrtowcs(contents, &mb_sequence, sizeof(contents), NULL); EXPECT_TRUE(mb_sequence == NULL); + return std::wstring(contents); #else + wchar_t contents[64]; std::wifstream file; file.open(filename.value().c_str()); EXPECT_TRUE(file.is_open()); file.getline(contents, arraysize(contents)); file.close(); -#endif return std::wstring(contents); +#endif } #if defined(OS_WIN)
diff --git a/src/base/i18n/file_util_icu.cc b/src/base/i18n/file_util_icu.cc index fc07d13..0dd1523 100644 --- a/src/base/i18n/file_util_icu.cc +++ b/src/base/i18n/file_util_icu.cc
@@ -159,7 +159,7 @@ // Windows uses UTF-16 encoding for filenames. U16_NEXT(file_name->data(), cursor, static_cast<int>(file_name->length()), code_point); -#elif defined(OS_POSIX) || defined(OS_STARBAORD) +#elif defined(OS_POSIX) || defined(OS_STARBOARD) // Linux doesn't actually define an encoding. It basically allows anything // except for a few special ASCII characters. unsigned char cur_char = static_cast<unsigned char>((*file_name)[cursor++]);
diff --git a/src/base/location.cc b/src/base/location.cc index b6ded2c..fe8581a 100644 --- a/src/base/location.cc +++ b/src/base/location.cc
@@ -92,9 +92,11 @@ BASE_EXPORT const void* GetProgramCounter() { #if defined(COMPILER_MSVC) return _ReturnAddress(); +#elif defined(COMPILER_SNC) + return __builtin_return_address(0); #elif defined(COMPILER_GCC) && !defined(__LB_PS3__) && !defined(__LB_WIIU__) return __builtin_extract_return_addr(__builtin_return_address(0)); -#endif // COMPILER_GCC +#endif // defined(COMPILER_MSVC) return NULL; }
diff --git a/src/base/logging.cc b/src/base/logging.cc index 6a135ea..d3a97e5 100644 --- a/src/base/logging.cc +++ b/src/base/logging.cc
@@ -406,6 +406,25 @@ return true; } +#if defined(OS_STARBOARD) +SbLogPriority LogLevelToStarboardLogPriority(int level) { + switch (level) { + case LOG_INFO: + return kSbLogPriorityInfo; + case LOG_WARNING: + return kSbLogPriorityWarning; + case LOG_ERROR: + case LOG_ERROR_REPORT: + return kSbLogPriorityError; + case LOG_FATAL: + return kSbLogPriorityFatal; + default: + NOTREACHED() << "Unrecognized log level."; + return kSbLogPriorityInfo; + } +} +#endif // defined(OS_STARBOARD) + } // namespace @@ -467,6 +486,11 @@ void SetMinLogLevel(int level) { min_log_level = std::min(LOG_ERROR_REPORT, level); + +#if defined(OS_STARBOARD) + starboard::logging::SetMinLogLevel( + LogLevelToStarboardLogPriority(std::min(LOG_FATAL, level))); +#endif } int GetMinLogLevel() { @@ -650,23 +674,7 @@ if (logging_destination == LOG_ONLY_TO_SYSTEM_DEBUG_LOG || logging_destination == LOG_TO_BOTH_FILE_AND_SYSTEM_DEBUG_LOG) { #if defined(OS_STARBOARD) - SbLogPriority priority = kSbLogPriorityUnknown; - switch (severity_) { - case LOG_INFO: - priority = kSbLogPriorityInfo; - break; - case LOG_WARNING: - priority = kSbLogPriorityWarning; - break; - case LOG_ERROR: - case LOG_ERROR_REPORT: - priority = kSbLogPriorityError; - break; - case LOG_FATAL: - priority = kSbLogPriorityFatal; - break; - } - SbLog(priority, str_newline.c_str()); + SbLog(LogLevelToStarboardLogPriority(severity_), str_newline.c_str()); #elif defined(OS_WIN) || defined(COBALT_WIN) OutputDebugStringA(str_newline.c_str()); #elif defined(OS_ANDROID) || defined(__LB_ANDROID__)
diff --git a/src/base/memory/aligned_memory.h b/src/base/memory/aligned_memory.h index 040b56d..8ea96b7 100644 --- a/src/base/memory/aligned_memory.h +++ b/src/base/memory/aligned_memory.h
@@ -99,7 +99,7 @@ #if defined(COMPILER_MSVC) _aligned_free(ptr); #elif defined(OS_STARBOARD) - SbMemoryFreeAligned(ptr); + SbMemoryDeallocateAligned(ptr); #else free(ptr); #endif
diff --git a/src/base/memory/scoped_ptr.h b/src/base/memory/scoped_ptr.h index 9ee780e..7f2d7dc 100644 --- a/src/base/memory/scoped_ptr.h +++ b/src/base/memory/scoped_ptr.h
@@ -395,7 +395,7 @@ public: inline void operator()(void* x) const { #if defined(OS_STARBOARD) - SbMemoryFree(x); + SbMemoryDeallocate(x); #else free(x); #endif
diff --git a/src/base/pickle.cc b/src/base/pickle.cc index 43121c9..e5050b9 100644 --- a/src/base/pickle.cc +++ b/src/base/pickle.cc
@@ -10,7 +10,7 @@ #if defined(OS_STARBOARD) #include "starboard/memory.h" #define realloc SbMemoryReallocate -#define free SbMemoryFree +#define free SbMemoryDeallocate #define memcpy SbMemoryCopy #else #include <stdlib.h>
diff --git a/src/base/platform_file_starboard.cc b/src/base/platform_file_starboard.cc index c28fd65..cc116c6 100644 --- a/src/base/platform_file_starboard.cc +++ b/src/base/platform_file_starboard.cc
@@ -161,21 +161,7 @@ int ReadPlatformFileAtCurrentPos(PlatformFile file, char *data, int size) { base::ThreadRestrictions::AssertIOAllowed(); - if (file < 0 || size < 0) - return -1; - - int bytes_read = 0; - int rv; - do { - rv = SbFileRead(file, data, size); - if (rv <= 0) { - break; - } - - bytes_read += rv; - } while (bytes_read < size); - - return bytes_read ? bytes_read : rv; + return SbFileReadAll(file, data, size); } int ReadPlatformFileNoBestEffort(PlatformFile file, @@ -251,22 +237,7 @@ const char* data, int size) { base::ThreadRestrictions::AssertIOAllowed(); - if (size < 0) { - return -1; - } - - int bytes_written = 0; - int result; - do { - result = SbFileWrite(file, data, size); - if (result <= 0) { - break; - } - - bytes_written += result; - } while (bytes_written < size); - - return bytes_written ? bytes_written : result; + return SbFileWriteAll(file, data, size); } int WritePlatformFileCurPosNoBestEffort(PlatformFile file,
diff --git a/src/base/platform_file_unittest.cc b/src/base/platform_file_unittest.cc index e73d930..607dc66 100644 --- a/src/base/platform_file_unittest.cc +++ b/src/base/platform_file_unittest.cc
@@ -197,16 +197,8 @@ // Make sure the file was extended. int64 file_size = 0; -#if defined(__LB_SHELL__) - // At this point, the file buffer may not have been flushed to disk, - // so reading the new file size from disk is flaky. Instead, read the file - // size from the file descriptor using GetPlatformFileInfo. - base::PlatformFileInfo file_info; - EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info)); - file_size = file_info.size; -#else + base::FlushPlatformFile(file); EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); -#endif EXPECT_EQ(kOffsetBeyondEndOfFile + kPartialWriteLength, file_size); // Make sure the file was zero-padded. @@ -246,6 +238,7 @@ const int kExtendedFileLength = 10; int64 file_size = 0; EXPECT_TRUE(base::TruncatePlatformFile(file, kExtendedFileLength)); + base::FlushPlatformFile(file); EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); EXPECT_EQ(kExtendedFileLength, file_size); @@ -261,6 +254,7 @@ // Truncate the file. const int kTruncatedFileLength = 2; EXPECT_TRUE(base::TruncatePlatformFile(file, kTruncatedFileLength)); + base::FlushPlatformFile(file); EXPECT_TRUE(file_util::GetFileSize(file_path, &file_size)); EXPECT_EQ(kTruncatedFileLength, file_size);
diff --git a/src/base/shared_memory.h b/src/base/shared_memory.h index bf77788..8589779 100644 --- a/src/base/shared_memory.h +++ b/src/base/shared_memory.h
@@ -67,7 +67,7 @@ } ~RefCountedMem() { #if defined(OS_STARBOARD) - SbMemoryFree(memory_); + SbMemoryDeallocate(memory_); #else free(memory_); #endif
diff --git a/src/base/stl_util.h b/src/base/stl_util.h index c612769..483fd53 100644 --- a/src/base/stl_util.h +++ b/src/base/stl_util.h
@@ -7,7 +7,7 @@ #include <algorithm> #include <functional> -#if defined(__LB_SHELL__) // TODO: Starboard? +#if defined(__LB_SHELL__) || defined(STARBOARD) #include <iterator> #endif #include <string>
diff --git a/src/base/string16.cc b/src/base/string16.cc index 930e09f..9c5acb7 100644 --- a/src/base/string16.cc +++ b/src/base/string16.cc
@@ -4,14 +4,9 @@ #include "base/string16.h" -#if defined(WCHAR_T_IS_UTF16) -#error This file should not be used on 2-byte wchar_t systems -// If this winds up being needed on 2-byte wchar_t systems, either the -// definitions below can be used, or the host system's wide character -// functions like wmemcmp can be wrapped. - -#elif defined(WCHAR_T_IS_UTF32) +// See discussion in string16.h: This is only needed on 32-bit wchar_t systems. +#if defined(WCHAR_T_IS_UTF32) #include <ostream>
diff --git a/src/base/stringprintf_unittest.cc b/src/base/stringprintf_unittest.cc index d8fdfee..143fc42 100644 --- a/src/base/stringprintf_unittest.cc +++ b/src/base/stringprintf_unittest.cc
@@ -180,7 +180,7 @@ // lbshell platforms do not support positional parameters, // and lbshell does not use the few parts of chromium that // leverage positional parameter support in the OS. -#if !defined(__LB_SHELL__) +#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD) // Test that the positional parameters work. TEST(StringPrintfTest, PositionalParameters) { std::string out;
diff --git a/src/base/test/test_file_util_starboard.cc b/src/base/test/test_file_util_starboard.cc index ef4cdad..f8127c6 100644 --- a/src/base/test/test_file_util_starboard.cc +++ b/src/base/test/test_file_util_starboard.cc
@@ -36,7 +36,7 @@ // Mostly a verbatim copy of CopyDirectory bool CopyRecursiveDirNoCache(const FilePath& source_dir, const FilePath& dest_dir) { - char top_dir[PATH_MAX]; + char top_dir[SB_FILE_MAX_PATH]; if (base::strlcpy(top_dir, source_dir.value().c_str(), arraysize(top_dir)) >= arraysize(top_dir)) { return false;
diff --git a/src/base/third_party/dmg_fp/dtoa.cc b/src/base/third_party/dmg_fp/dtoa.cc index 1339d3f..3c5d5a5 100644 --- a/src/base/third_party/dmg_fp/dtoa.cc +++ b/src/base/third_party/dmg_fp/dtoa.cc
@@ -205,7 +205,7 @@ #if defined(STARBOARD) #include "starboard/memory.h" #define MALLOC SbMemoryAllocate -#define FREE SbMemoryFree +#define FREE SbMemoryDeallocate #else #include "stdlib.h" #include "string.h"
diff --git a/src/base/third_party/nspr/prcpucfg_starboard.h b/src/base/third_party/nspr/prcpucfg_starboard.h index 5bdf40d..3aaa298 100644 --- a/src/base/third_party/nspr/prcpucfg_starboard.h +++ b/src/base/third_party/nspr/prcpucfg_starboard.h
@@ -55,7 +55,7 @@ # define IS_64 #endif -#if SB_IS(ARCH_PPC) && SB_IS(32_BIT) +#if SB_IS(ARCH_PPC) #define PR_BYTES_PER_BYTE 1 #define PR_BYTES_PER_SHORT 2 #define PR_BYTES_PER_INT 4
diff --git a/src/base/threading/thread_local.h b/src/base/threading/thread_local.h index 762d77b..fb6dfb2 100644 --- a/src/base/threading/thread_local.h +++ b/src/base/threading/thread_local.h
@@ -73,7 +73,7 @@ static void AllocateSlot(SlotType& slot); static void FreeSlot(SlotType& slot); - static void* GetValueFromSlot(SlotType& slot); + static void* GetValueFromSlot(const SlotType& slot); static void SetValueInSlot(SlotType& slot, void* value); }; @@ -90,7 +90,7 @@ internal::ThreadLocalPlatform::FreeSlot(slot_); } - Type* Get() { + Type* Get() const { return static_cast<Type*>( internal::ThreadLocalPlatform::GetValueFromSlot(slot_)); } @@ -113,7 +113,7 @@ ThreadLocalBoolean() { } ~ThreadLocalBoolean() { } - bool Get() { + bool Get() const { return tlp_.Get() != NULL; }
diff --git a/src/base/threading/thread_local_posix.cc b/src/base/threading/thread_local_posix.cc index 4951006..1b381c2 100644 --- a/src/base/threading/thread_local_posix.cc +++ b/src/base/threading/thread_local_posix.cc
@@ -25,7 +25,7 @@ } // static -void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) { +void* ThreadLocalPlatform::GetValueFromSlot(const SlotType& slot) { return pthread_getspecific(slot); }
diff --git a/src/base/threading/thread_local_starboard.cc b/src/base/threading/thread_local_starboard.cc index e3a96d6..1234425 100644 --- a/src/base/threading/thread_local_starboard.cc +++ b/src/base/threading/thread_local_starboard.cc
@@ -33,7 +33,7 @@ } // static -void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) { +void* ThreadLocalPlatform::GetValueFromSlot(const SlotType& slot) { return SbThreadGetLocalValue(slot); }
diff --git a/src/base/threading/thread_local_win.cc b/src/base/threading/thread_local_win.cc index 56d3a3a..7e82c90 100644 --- a/src/base/threading/thread_local_win.cc +++ b/src/base/threading/thread_local_win.cc
@@ -26,7 +26,7 @@ } // static -void* ThreadLocalPlatform::GetValueFromSlot(SlotType& slot) { +void* ThreadLocalPlatform::GetValueFromSlot(const SlotType& slot) { return TlsGetValue(slot); }
diff --git a/src/base/time.h b/src/base/time.h index 38bc53a..9fde0e4 100644 --- a/src/base/time.h +++ b/src/base/time.h
@@ -593,6 +593,15 @@ // SHOULD ONLY BE USED WHEN IT IS REALLY NEEDED. static TimeTicks HighResNow(); + // Returns the amount of time the current thread has spent in the execution + // state (i.e. not pre-empted or waiting on some event). If this is not + // available, then HighResNow() will be returned. + static TimeTicks ThreadNow(); + + // Returns whether ThreadNow() is implemented to report thread time (as + // opposed to HighResNow()). + static bool HasThreadNow(); + // Returns the current system trace time or, if none is defined, the current // high-res time (i.e. HighResNow()). On systems where a global trace clock // is defined, timestamping TraceEvents's with this value guarantees
diff --git a/src/base/time_mac.cc b/src/base/time_mac.cc index 883a35b..327f9e6 100644 --- a/src/base/time_mac.cc +++ b/src/base/time_mac.cc
@@ -192,6 +192,16 @@ } // static +TimeTicks TimeTicks::ThreadNow() { + return HighResNow(); +} + +// static +bool TimeTicks::HasThreadNow() { + return false; +} + +// static TimeTicks TimeTicks::NowFromSystemTraceTime() { return HighResNow(); }
diff --git a/src/base/time_posix.cc b/src/base/time_posix.cc index 80b1c4e..d053ff2 100644 --- a/src/base/time_posix.cc +++ b/src/base/time_posix.cc
@@ -267,6 +267,27 @@ return Now(); } +// static +TimeTicks TimeTicks::ThreadNow() { + uint64_t absolute_micro; + + struct timespec ts; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) != 0) { + return HighResNow(); + } + + absolute_micro = + (static_cast<int64>(ts.tv_sec) * Time::kMicrosecondsPerSecond) + + (static_cast<int64>(ts.tv_nsec) / Time::kNanosecondsPerMicrosecond); + + return TimeTicks(absolute_micro); +} + +// static +bool TimeTicks::HasThreadNow() { + return true; +} + #if defined(OS_CHROMEOS) // Force definition of the system trace clock; it is a chromeos-only api // at the moment and surfacing it in the right place requires mucking
diff --git a/src/base/time_starboard.cc b/src/base/time_starboard.cc index 2e7785e..0327166 100644 --- a/src/base/time_starboard.cc +++ b/src/base/time_starboard.cc
@@ -97,6 +97,24 @@ } // static +TimeTicks TimeTicks::ThreadNow() { +#if SB_VERSION(3) && SB_HAS(TIME_THREAD_NOW) + return TimeTicks(SbTimeGetMonotonicThreadNow()); +#else + return HighResNow(); +#endif +} + +// static +bool TimeTicks::HasThreadNow() { +#if SB_VERSION(3) && SB_HAS(TIME_THREAD_NOW) + return true; +#else + return false; +#endif +} + +// static TimeTicks TimeTicks::NowFromSystemTraceTime() { return HighResNow(); }
diff --git a/src/base/time_win.cc b/src/base/time_win.cc index 6d8e432..294a564 100644 --- a/src/base/time_win.cc +++ b/src/base/time_win.cc
@@ -460,6 +460,16 @@ } // static +TimeTicks TimeTicks::ThreadNow() { + return HighResNow(); +} + +// static +bool TimeTicks::HasThreadNow() { + return false; +} + +// static TimeTicks TimeTicks::NowFromSystemTraceTime() { return HighResNow(); }
diff --git a/src/base/tools_sanity_unittest.cc b/src/base/tools_sanity_unittest.cc index a901f59..074a13e 100644 --- a/src/base/tools_sanity_unittest.cc +++ b/src/base/tools_sanity_unittest.cc
@@ -15,7 +15,7 @@ #if defined(OS_STARBOARD) #include "starboard/memory.h" -#define free SbMemoryFree +#define free SbMemoryDeallocate #define malloc SbMemoryAllocate #endif
diff --git a/src/cobalt/audio/async_audio_decoder.cc b/src/cobalt/audio/async_audio_decoder.cc index cb23a87..2aa7a62 100644 --- a/src/cobalt/audio/async_audio_decoder.cc +++ b/src/cobalt/audio/async_audio_decoder.cc
@@ -19,6 +19,7 @@ #include "base/bind.h" #include "base/logging.h" #include "cobalt/audio/audio_file_reader.h" +#include "cobalt/audio/audio_helpers.h" namespace cobalt { namespace audio { @@ -30,15 +31,17 @@ void Decode( const uint8* audio_data, size_t size, const AsyncAudioDecoder::DecodeFinishCallback& decode_finish_callback) { - scoped_ptr<AudioFileReader> reader( - AudioFileReader::TryCreate(audio_data, size)); + scoped_ptr<AudioFileReader> reader(AudioFileReader::TryCreate( + audio_data, size, GetPreferredOutputSampleType())); if (reader) { - decode_finish_callback.Run( - reader->sample_rate(), reader->number_of_frames(), - reader->number_of_channels(), reader->sample_data()); + decode_finish_callback.Run(reader->sample_rate(), + reader->number_of_frames(), + reader->number_of_channels(), + reader->sample_data(), reader->sample_type()); } else { - decode_finish_callback.Run(0.f, 0, 0, scoped_array<uint8>()); + decode_finish_callback.Run(0.f, 0, 0, scoped_array<uint8>(), + kSampleTypeFloat32); } }
diff --git a/src/cobalt/audio/async_audio_decoder.h b/src/cobalt/audio/async_audio_decoder.h index b78d2a7..3827260 100644 --- a/src/cobalt/audio/async_audio_decoder.h +++ b/src/cobalt/audio/async_audio_decoder.h
@@ -20,6 +20,7 @@ #include "base/callback.h" #include "base/threading/thread.h" #include "cobalt/audio/audio_buffer.h" +#include "cobalt/audio/audio_helpers.h" #include "cobalt/dom/array_buffer.h" namespace cobalt { @@ -27,9 +28,10 @@ class AsyncAudioDecoder { public: - typedef base::Callback<void( - float sample_rate, int32 number_of_frames, int32 number_of_channels, - scoped_array<uint8> channels_data)> DecodeFinishCallback; + typedef base::Callback<void(float sample_rate, int32 number_of_frames, + int32 number_of_channels, + scoped_array<uint8> channels_data, + SampleType sample_type)> DecodeFinishCallback; AsyncAudioDecoder();
diff --git a/src/cobalt/audio/audio.gyp b/src/cobalt/audio/audio.gyp index f13d2c3..350a599 100644 --- a/src/cobalt/audio/audio.gyp +++ b/src/cobalt/audio/audio.gyp
@@ -37,6 +37,7 @@ 'audio_file_reader.h', 'audio_file_reader_wav.cc', 'audio_file_reader_wav.h', + 'audio_helpers.h', 'audio_node.cc', 'audio_node.h', 'audio_node_input.cc',
diff --git a/src/cobalt/audio/audio_buffer.cc b/src/cobalt/audio/audio_buffer.cc index f6b8d6b..a93eba2 100644 --- a/src/cobalt/audio/audio_buffer.cc +++ b/src/cobalt/audio/audio_buffer.cc
@@ -16,6 +16,7 @@ #include "cobalt/audio/audio_buffer.h" +#include "cobalt/audio/audio_helpers.h" #include "cobalt/dom/dom_exception.h" namespace cobalt { @@ -24,35 +25,54 @@ AudioBuffer::AudioBuffer(script::EnvironmentSettings* settings, float sample_rate, int32 number_of_frames, int32 number_of_channels, - scoped_array<uint8> channels_data) - : sample_rate_(sample_rate), length_(number_of_frames) { + scoped_array<uint8> channels_data, + SampleType sample_type) + : sample_rate_(sample_rate), + length_(number_of_frames), + sample_type_(sample_type) { DCHECK_GT(sample_rate_, 0); DCHECK_GT(length_, 0); DCHECK_GT(number_of_channels, 0); // Create an ArrayBuffer stores sample data from all channels. + const uint32 length = + number_of_frames * number_of_channels * + (sample_type == kSampleTypeFloat32 ? sizeof(float) : sizeof(int16)); scoped_refptr<dom::ArrayBuffer> array_buffer(new dom::ArrayBuffer( - settings, dom::ArrayBuffer::kFromHeap, channels_data.Pass(), - number_of_frames * number_of_channels * sizeof(float))); + settings, dom::ArrayBuffer::kFromHeap, channels_data.Pass(), length)); - channels_data_.resize(static_cast<size_t>(number_of_channels)); - // Each channel should have |number_of_frames * sizeof(float)| bytes. We - // create |number_of_channels| of Float32Array as views into the above - // ArrayBuffer, this doesn't need any extra allocation. - uint32 start_offset_in_bytes = 0; - for (int32 i = 0; i < number_of_channels; ++i) { - channels_data_[static_cast<size_t>(i)] = - new dom::Float32Array(settings, array_buffer, start_offset_in_bytes, + // Each channel should have |number_of_frames * size_of_sample_type| bytes. + // We create |number_of_channels| of {Float32,Int16}Array as views into the + // above ArrayBuffer. This does not need any extra allocation. + if (sample_type == kSampleTypeFloat32) { + channels_data_.resize(static_cast<size_t>(number_of_channels)); + uint32 start_offset_in_bytes = 0; + for (int32 i = 0; i < number_of_channels; ++i) { + channels_data_[static_cast<size_t>(i)] = + new dom::Float32Array(settings, array_buffer, start_offset_in_bytes, + static_cast<uint32>(number_of_frames), NULL); + start_offset_in_bytes += number_of_frames * sizeof(float); + } + } else if (sample_type == kSampleTypeInt16) { + channels_int16_data_.resize(static_cast<size_t>(number_of_channels)); + uint32 start_offset_in_bytes = 0; + for (int32 i = 0; i < number_of_channels; ++i) { + channels_int16_data_[static_cast<size_t>(i)] = + new dom::Int16Array(settings, array_buffer, start_offset_in_bytes, static_cast<uint32>(number_of_frames), NULL); - start_offset_in_bytes += number_of_frames * sizeof(float); + start_offset_in_bytes += number_of_frames * sizeof(int16); + } + } else { + NOTREACHED(); } } scoped_refptr<dom::Float32Array> AudioBuffer::GetChannelData( uint32 channel_index, script::ExceptionState* exception_state) const { + DCHECK_EQ(sample_type_, kSampleTypeFloat32); // The index value MUST be less than number_of_channels() or an INDEX_SIZE_ERR // exception MUST be thrown. - if (channel_index >= static_cast<uint32>(number_of_channels())) { + if (channel_index >= channels_data_.size()) { dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state); return NULL; } @@ -60,5 +80,18 @@ return channels_data_[channel_index]; } +scoped_refptr<dom::Int16Array> AudioBuffer::GetChannelDataInt16( + uint32 channel_index, script::ExceptionState* exception_state) const { + DCHECK_EQ(sample_type_, kSampleTypeInt16); + // The index value MUST be less than number_of_channels() or an INDEX_SIZE_ERR + // exception MUST be thrown. + if (channel_index >= channels_int16_data_.size()) { + dom::DOMException::Raise(dom::DOMException::kIndexSizeErr, exception_state); + return NULL; + } + + return channels_int16_data_[channel_index]; +} + } // namespace audio } // namespace cobalt
diff --git a/src/cobalt/audio/audio_buffer.h b/src/cobalt/audio/audio_buffer.h index a1fab1e..c58fef5 100644 --- a/src/cobalt/audio/audio_buffer.h +++ b/src/cobalt/audio/audio_buffer.h
@@ -21,7 +21,9 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" // For scoped_array +#include "cobalt/audio/audio_helpers.h" #include "cobalt/dom/float32_array.h" +#include "cobalt/dom/int16_array.h" #include "cobalt/script/environment_settings.h" #include "cobalt/script/wrappable.h" @@ -45,7 +47,7 @@ // half. AudioBuffer(script::EnvironmentSettings* settings, float sample_rate, int32 number_of_frames, int32 number_of_channels, - scoped_array<uint8> channels_data); + scoped_array<uint8> channels_data, SampleType sample_type); // Web API: AudioBuffer // @@ -60,22 +62,35 @@ // The number of discrete audio channels. int32 number_of_channels() const { - return static_cast<int32>(channels_data_.size()); + if (sample_type_ == kSampleTypeFloat32) { + return static_cast<int32>(channels_data_.size()); + } else if (sample_type_ == kSampleTypeInt16) { + return static_cast<int32>(channels_int16_data_.size()); + } else { + NOTREACHED(); + return 0; + } } // Represents the PCM audio data for the specific channel. scoped_refptr<dom::Float32Array> GetChannelData( uint32 channel_index, script::ExceptionState* exception_state) const; + scoped_refptr<dom::Int16Array> GetChannelDataInt16( + uint32 channel_index, script::ExceptionState* exception_state) const; + DEFINE_WRAPPABLE_TYPE(AudioBuffer); private: typedef std::vector<scoped_refptr<dom::Float32Array> > Float32ArrayVector; + typedef std::vector<scoped_refptr<dom::Int16Array> > Int16ArrayVector; float sample_rate_; int32 length_; + SampleType sample_type_; Float32ArrayVector channels_data_; + Int16ArrayVector channels_int16_data_; DISALLOW_COPY_AND_ASSIGN(AudioBuffer); };
diff --git a/src/cobalt/audio/audio_buffer_source_node.cc b/src/cobalt/audio/audio_buffer_source_node.cc index 6f3538f..222ab7a 100644 --- a/src/cobalt/audio/audio_buffer_source_node.cc +++ b/src/cobalt/audio/audio_buffer_source_node.cc
@@ -94,7 +94,7 @@ } scoped_ptr<ShellAudioBus> AudioBufferSourceNode::PassAudioBusFromSource( - int32 number_of_frames) { + int32 number_of_frames, SampleType sample_type) { // This is called by Audio thread. audio_lock()->AssertLocked(); @@ -108,21 +108,42 @@ size_t channels = static_cast<size_t>(buffer_->number_of_channels()); - std::vector<float*> audio_buffer; - for (size_t i = 0; i < channels; ++i) { - scoped_refptr<dom::Float32Array> buffer_data = - buffer_->GetChannelData(static_cast<uint32>(i), NULL); - scoped_refptr<dom::Float32Array> sub_array = buffer_data->Subarray( - NULL, read_index_, read_index_ + number_of_frames); - audio_buffer.push_back(sub_array->data()); + if (sample_type == kSampleTypeFloat32) { + std::vector<float*> audio_buffer(channels, NULL); + for (size_t i = 0; i < channels; ++i) { + scoped_refptr<dom::Float32Array> buffer_data = + buffer_->GetChannelData(static_cast<uint32>(i), NULL); + scoped_refptr<dom::Float32Array> sub_array = buffer_data->Subarray( + NULL, read_index_, read_index_ + number_of_frames); + audio_buffer[i] = sub_array->data(); + } + + read_index_ += number_of_frames; + + scoped_ptr<ShellAudioBus> audio_bus( + new ShellAudioBus(static_cast<size_t>(number_of_frames), audio_buffer)); + + return audio_bus.Pass(); + } else if (sample_type == kSampleTypeInt16) { + std::vector<int16*> audio_buffer(channels, NULL); + for (size_t i = 0; i < channels; ++i) { + scoped_refptr<dom::Int16Array> buffer_data = + buffer_->GetChannelDataInt16(static_cast<uint32>(i), NULL); + scoped_refptr<dom::Int16Array> sub_array = buffer_data->Subarray( + NULL, read_index_, read_index_ + number_of_frames); + audio_buffer[i] = sub_array->data(); + } + + read_index_ += number_of_frames; + + scoped_ptr<ShellAudioBus> audio_bus( + new ShellAudioBus(static_cast<size_t>(number_of_frames), audio_buffer)); + + return audio_bus.Pass(); } - read_index_ += number_of_frames; - - scoped_ptr<ShellAudioBus> audio_bus( - new ShellAudioBus(static_cast<size_t>(number_of_frames), audio_buffer)); - - return audio_bus.Pass(); + NOTREACHED(); + return scoped_ptr<ShellAudioBus>(); } } // namespace audio
diff --git a/src/cobalt/audio/audio_buffer_source_node.h b/src/cobalt/audio/audio_buffer_source_node.h index ee808d3..ce4f42f 100644 --- a/src/cobalt/audio/audio_buffer_source_node.h +++ b/src/cobalt/audio/audio_buffer_source_node.h
@@ -72,7 +72,7 @@ } scoped_ptr<ShellAudioBus> PassAudioBusFromSource( - int32 number_of_frames) OVERRIDE; + int32 number_of_frames, SampleType sample_type) OVERRIDE; DEFINE_WRAPPABLE_TYPE(AudioBufferSourceNode);
diff --git a/src/cobalt/audio/audio_context.cc b/src/cobalt/audio/audio_context.cc index 178d7b6..0962977 100644 --- a/src/cobalt/audio/audio_context.cc +++ b/src/cobalt/audio/audio_context.cc
@@ -85,13 +85,14 @@ void AudioContext::DecodeFinish(int callback_id, float sample_rate, int32 number_of_frames, int32 number_of_channels, - scoped_array<uint8> channels_data) { + scoped_array<uint8> channels_data, + SampleType sample_type) { if (!main_message_loop_->BelongsToCurrentThread()) { main_message_loop_->PostTask( FROM_HERE, base::Bind(&AudioContext::DecodeFinish, weak_this_, callback_id, sample_rate, number_of_frames, number_of_channels, - base::Passed(&channels_data))); + base::Passed(&channels_data), sample_type)); return; } @@ -105,7 +106,7 @@ if (channels_data) { const scoped_refptr<AudioBuffer>& audio_buffer = new AudioBuffer(info->env_settings, sample_rate, number_of_frames, - number_of_channels, channels_data.Pass()); + number_of_channels, channels_data.Pass(), sample_type); info->success_callback.value().Run(audio_buffer); } else if (info->error_callback) { info->error_callback.value().value().Run();
diff --git a/src/cobalt/audio/audio_context.h b/src/cobalt/audio/audio_context.h index 5cd34a8..9b3ac6f 100644 --- a/src/cobalt/audio/audio_context.h +++ b/src/cobalt/audio/audio_context.h
@@ -165,8 +165,8 @@ void DecodeAudioDataInternal(scoped_ptr<DecodeCallbackInfo> info); void DecodeFinish(int callback_id, float sample_rate, int32 number_of_frames, - int32 number_of_channels, - scoped_array<uint8> channels_data); + int32 number_of_channels, scoped_array<uint8> channels_data, + SampleType sample_type); base::WeakPtrFactory<AudioContext> weak_ptr_factory_; // We construct a WeakPtr upon AudioContext's construction in order to
diff --git a/src/cobalt/audio/audio_destination_node.cc b/src/cobalt/audio/audio_destination_node.cc index 59d80df..e0eda2b 100644 --- a/src/cobalt/audio/audio_destination_node.cc +++ b/src/cobalt/audio/audio_destination_node.cc
@@ -38,8 +38,6 @@ : AudioNode(context), max_channel_count_(kMaxChannelCount) { AudioLock::AutoLock lock(audio_lock()); - audio_device_.reset( - new AudioDevice(static_cast<int>(channel_count(NULL)), this)); AddInput(new AudioNodeInput(this)); } @@ -54,6 +52,15 @@ RemoveAllInputs(); } +void AudioDestinationNode::OnInputNodeConnected() { + audio_lock()->AssertLocked(); + + if (!audio_device_) { + audio_device_.reset( + new AudioDevice(static_cast<int>(channel_count(NULL)), this)); + } +} + void AudioDestinationNode::FillAudioBus(ShellAudioBus* audio_bus, bool* silence) { // This is called by Audio thread.
diff --git a/src/cobalt/audio/audio_destination_node.h b/src/cobalt/audio/audio_destination_node.h index 7a760a0..50bfe7e 100644 --- a/src/cobalt/audio/audio_destination_node.h +++ b/src/cobalt/audio/audio_destination_node.h
@@ -20,6 +20,7 @@ #include <vector> #include "cobalt/audio/audio_device.h" +#include "cobalt/audio/audio_helpers.h" #include "cobalt/audio/audio_node.h" #include "media/base/shell_audio_bus.h" @@ -47,8 +48,9 @@ uint32 max_channel_count() const { return max_channel_count_; } // From AudioNode. - scoped_ptr<ShellAudioBus> PassAudioBusFromSource( - int32 /*number_of_frames*/) OVERRIDE { + void OnInputNodeConnected() OVERRIDE; + scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32 /*number_of_frames*/, + SampleType) OVERRIDE { NOTREACHED(); return scoped_ptr<ShellAudioBus>(); }
diff --git a/src/cobalt/audio/audio_device.cc b/src/cobalt/audio/audio_device.cc index 8ab8b57..e666970 100644 --- a/src/cobalt/audio/audio_device.cc +++ b/src/cobalt/audio/audio_device.cc
@@ -18,6 +18,7 @@ #include "base/debug/trace_event.h" #include "base/memory/scoped_ptr.h" +#include "cobalt/audio/audio_helpers.h" #if defined(OS_STARBOARD) #include "starboard/audio_sink.h" #include "starboard/configuration.h" @@ -46,41 +47,6 @@ #if defined(SB_USE_SB_AUDIO_SINK) -namespace { -// Helper function to compute the size of the two valid starboard audio sample -// types. -size_t GetSampleSize(SbMediaAudioSampleType sample_type) { - switch (sample_type) { - case kSbMediaAudioSampleTypeFloat32: - return sizeof(float); - case kSbMediaAudioSampleTypeInt16: - return sizeof(int16); - } - NOTREACHED(); - return 0u; -} - -const float kMaxInt16AsFloat32 = 32767.0f; - -template <typename SourceType, typename DestType> -DestType ConvertSample(SourceType sample); - -template <> -int16 ConvertSample<float, int16>(float sample) { - if (!(-1.0 <= sample && sample <= 1.0)) { - DLOG(WARNING) << - "Sample of type float32 must lie on interval [-1.0, 1.0], got: " << - sample << "."; - } - return static_cast<int16>(sample * kMaxInt16AsFloat32); -} - -template <> -float ConvertSample<float, float>(float sample) { - return sample; -} -} // namespace - class AudioDevice::Impl { public: Impl(int number_of_channels, RenderCallback* callback); @@ -98,7 +64,7 @@ void FillOutputAudioBus(); - template <typename OutputType> + template <typename InputType, typename OutputType> inline void FillOutputAudioBusForType(); int number_of_channels_; @@ -126,16 +92,14 @@ // AudioDevice::Impl. AudioDevice::Impl::Impl(int number_of_channels, RenderCallback* callback) : number_of_channels_(number_of_channels), - output_sample_type_( - SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32) - ? kSbMediaAudioSampleTypeFloat32 - : kSbMediaAudioSampleTypeInt16), + output_sample_type_(GetPreferredOutputStarboardSampleType()), render_callback_(callback), input_audio_bus_(static_cast<size_t>(number_of_channels), static_cast<size_t>(kRenderBufferSizeFrames), - ShellAudioBus::kFloat32, ShellAudioBus::kPlanar), - output_frame_buffer_(new uint8[kFramesPerChannel * number_of_channels_ * - GetSampleSize(output_sample_type_)]), + GetPreferredOutputSampleType(), ShellAudioBus::kPlanar), + output_frame_buffer_( + new uint8[kFramesPerChannel * number_of_channels_ * + GetStarboardSampleTypeSize(output_sample_type_)]), frames_rendered_(0), frames_consumed_(0), was_silence_last_update_(false), @@ -147,7 +111,7 @@ kSbMediaAudioFrameStorageTypeInterleaved)) << "Only interleaved frame storage is supported."; DCHECK(SbAudioSinkIsAudioSampleTypeSupported(output_sample_type_)) - << "Output sample type " << output_sample_type_ << " is not supported"; + << "Output sample type " << output_sample_type_ << " is not supported."; frame_buffers_[0] = output_frame_buffer_.get(); audio_sink_ = SbAudioSinkCreate( @@ -231,7 +195,7 @@ frames_consumed_ += frames_consumed; } -template <typename OutputType> +template <typename InputType, typename OutputType> inline void AudioDevice::Impl::FillOutputAudioBusForType() { // Determine the offset into the audio bus that represents the tail of // buffered data. @@ -242,8 +206,10 @@ output_buffer += channel_offset * number_of_channels_; for (size_t frame = 0; frame < kRenderBufferSizeFrames; ++frame) { for (size_t channel = 0; channel < input_audio_bus_.channels(); ++channel) { - *output_buffer = ConvertSample<float, OutputType>( - input_audio_bus_.GetFloat32Sample(channel, frame)); + *output_buffer = ConvertSample<InputType, OutputType>( + input_audio_bus_ + .GetSampleForType<InputType, media::ShellAudioBus::kPlanar>( + channel, frame)); ++output_buffer; } } @@ -251,10 +217,20 @@ void AudioDevice::Impl::FillOutputAudioBus() { TRACE_EVENT0("cobalt::audio", "AudioDevice::Impl::FillOutputAudioBus()"); - if (output_sample_type_ == kSbMediaAudioSampleTypeFloat32) { - FillOutputAudioBusForType<float>(); - } else if (output_sample_type_ == kSbMediaAudioSampleTypeInt16) { - FillOutputAudioBusForType<int16>(); + + const bool is_input_int16 = + input_audio_bus_.sample_type() == media::ShellAudioBus::kInt16; + const bool is_output_int16 = + output_sample_type_ == kSbMediaAudioSampleTypeInt16; + + if (is_input_int16 && is_output_int16) { + FillOutputAudioBusForType<int16, int16>(); + } else if (!is_input_int16 && is_output_int16) { + FillOutputAudioBusForType<float, int16>(); + } else if (is_input_int16 && !is_output_int16) { + FillOutputAudioBusForType<int16, float>(); + } else if (!is_input_int16 && !is_output_int16) { + FillOutputAudioBusForType<float, float>(); } else { NOTREACHED(); } @@ -329,7 +305,7 @@ channel_layout, GetAudioHardwareSampleRate(), bytes_per_sample * 8, kRenderBufferSizeFrames); - // Create 1 channel audio bus due to we only support interleaved. + // Create 1 channel audio bus since we only support interleaved. output_audio_bus_ = AudioBus::Create(1, kFramesPerChannel * number_of_channels);
diff --git a/src/cobalt/audio/audio_file_reader.cc b/src/cobalt/audio/audio_file_reader.cc index ed61830..a858789 100644 --- a/src/cobalt/audio/audio_file_reader.cc +++ b/src/cobalt/audio/audio_file_reader.cc
@@ -23,9 +23,10 @@ // static scoped_ptr<AudioFileReader> AudioFileReader::TryCreate(const uint8* data, - size_t size) { + size_t size, + SampleType sample_type) { // Try to create other type of audio file reader. - return AudioFileReaderWAV::TryCreate(data, size).Pass(); + return AudioFileReaderWAV::TryCreate(data, size, sample_type).Pass(); } } // namespace audio
diff --git a/src/cobalt/audio/audio_file_reader.h b/src/cobalt/audio/audio_file_reader.h index 4897878..2d35a25 100644 --- a/src/cobalt/audio/audio_file_reader.h +++ b/src/cobalt/audio/audio_file_reader.h
@@ -19,6 +19,8 @@ #include "base/memory/scoped_ptr.h" // For scoped_array +#include "cobalt/audio/audio_helpers.h" + namespace cobalt { namespace audio { @@ -26,7 +28,8 @@ public: virtual ~AudioFileReader() {} - static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size); + static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size, + SampleType sample_type); // Returns the sample data stored as float sample in planar form. Note that // this function transfers the ownership of the data to the caller so it can @@ -35,6 +38,7 @@ virtual float sample_rate() const = 0; virtual int32 number_of_frames() const = 0; virtual int32 number_of_channels() const = 0; + virtual SampleType sample_type() const = 0; }; } // namespace audio
diff --git a/src/cobalt/audio/audio_file_reader_wav.cc b/src/cobalt/audio/audio_file_reader_wav.cc index 322fa0d..d0875f2 100644 --- a/src/cobalt/audio/audio_file_reader_wav.cc +++ b/src/cobalt/audio/audio_file_reader_wav.cc
@@ -46,15 +46,15 @@ } // namespace // static -scoped_ptr<AudioFileReader> AudioFileReaderWAV::TryCreate(const uint8* data, - size_t size) { +scoped_ptr<AudioFileReader> AudioFileReaderWAV::TryCreate( + const uint8* data, size_t size, SampleType sample_type) { // Need at least the |kWAVChunkSize| bytes for this to be a WAV. if (size < kWAVChunkSize) { return scoped_ptr<AudioFileReader>(); } scoped_ptr<AudioFileReaderWAV> audio_file_reader_wav( - new AudioFileReaderWAV(data, size)); + new AudioFileReaderWAV(data, size, sample_type)); if (!audio_file_reader_wav->is_valid()) { return scoped_ptr<AudioFileReader>(); @@ -63,8 +63,12 @@ return make_scoped_ptr<AudioFileReader>(audio_file_reader_wav.release()); } -AudioFileReaderWAV::AudioFileReaderWAV(const uint8* data, size_t size) - : sample_rate_(0.f), number_of_frames_(0), number_of_channels_(0) { +AudioFileReaderWAV::AudioFileReaderWAV(const uint8* data, size_t size, + SampleType sample_type) + : sample_rate_(0.f), + number_of_frames_(0), + number_of_channels_(0), + sample_type_(sample_type) { DCHECK_GE(size, kWAVRIFFChunkHeaderSize); if (ParseRIFFHeader(data, size)) { @@ -149,6 +153,10 @@ // Load channel count. number_of_channels_ = load_uint16_little_endian(data + offset + 2); + if (number_of_channels_ == 0) { + DLOG(ERROR) << "No channel on WAV."; + return false; + } // Load sample rate. sample_rate_ = @@ -175,32 +183,51 @@ const uint8* data_samples = data + offset; // Set number of frames based on size of data chunk. - int32 bytes_per_src_sample = + const int32 bytes_per_src_sample = static_cast<int32>(is_sample_in_float ? sizeof(float) : sizeof(int16)); number_of_frames_ = static_cast<int32>(size / (bytes_per_src_sample * number_of_channels_)); + const int32 bytes_per_dest_sample = + static_cast<int32>(GetSampleTypeSize(sample_type_)); + const bool is_dest_float = sample_type_ == kSampleTypeFloat32; - // We always store audio samples in float. - sample_data_.reset( - new uint8[number_of_frames_ * number_of_channels_ * sizeof(float)]); + // We store audio samples in the current platform's preferred format. + sample_data_.reset(new uint8[static_cast<size_t>( + number_of_frames_ * number_of_channels_ * bytes_per_dest_sample)]); - // The source data is stored interleaved. We need to convert it into planar. - float* dest_sample = reinterpret_cast<float*>(sample_data_.get()); + // Here we handle all 4 possible conversion cases. Also note that the + // source data is stored interleaved, and that need to convert it to planar. + uint8* dest_sample = sample_data_.get(); for (int32 i = 0; i < number_of_channels_; ++i) { const uint8* src_samples = data_samples + i * bytes_per_src_sample; for (int32 j = 0; j < number_of_frames_; ++j) { - float sample; - if (is_sample_in_float) { - uint32 sample_as_uint32 = load_uint32_little_endian(src_samples); - sample = bit_cast<float>(sample_as_uint32); + if (is_dest_float) { + float sample; + if (is_sample_in_float) { + uint32 sample_as_uint32 = load_uint32_little_endian(src_samples); + sample = bit_cast<float>(sample_as_uint32); + } else { + uint16 sample_pcm_unsigned = load_uint16_little_endian(src_samples); + int16 sample_pcm = bit_cast<int16>(sample_pcm_unsigned); + sample = ConvertSample<int16, float>(sample_pcm); + } + reinterpret_cast<float*>(dest_sample)[i * number_of_frames_ + j] = + sample; + src_samples += bytes_per_src_sample * number_of_channels_; } else { - uint16 sample_pcm_unsigned = load_uint16_little_endian(src_samples); - int16 sample_pcm = bit_cast<int16>(sample_pcm_unsigned); - sample = static_cast<float>(sample_pcm) / 32768.0f; + int16 sample; + if (is_sample_in_float) { + uint32 sample_as_uint32 = load_uint32_little_endian(src_samples); + float value = bit_cast<float>(sample_as_uint32); + sample = ConvertSample<float, int16>(value); + } else { + sample = bit_cast<int16>(load_uint16_little_endian(src_samples)); + } + reinterpret_cast<int16*>(dest_sample)[i * number_of_frames_ + j] = + sample; + src_samples += bytes_per_src_sample * number_of_channels_; } - dest_sample[i * number_of_frames_ + j] = sample; - src_samples += bytes_per_src_sample * number_of_channels_; } }
diff --git a/src/cobalt/audio/audio_file_reader_wav.h b/src/cobalt/audio/audio_file_reader_wav.h index cab1a1d..dc3699a 100644 --- a/src/cobalt/audio/audio_file_reader_wav.h +++ b/src/cobalt/audio/audio_file_reader_wav.h
@@ -18,6 +18,7 @@ #define COBALT_AUDIO_AUDIO_FILE_READER_WAV_H_ #include "cobalt/audio/audio_file_reader.h" +#include "cobalt/audio/audio_helpers.h" namespace cobalt { namespace audio { @@ -26,15 +27,17 @@ // http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html class AudioFileReaderWAV : public AudioFileReader { public: - static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size); + static scoped_ptr<AudioFileReader> TryCreate(const uint8* data, size_t size, + SampleType sample_type); scoped_array<uint8> sample_data() OVERRIDE { return sample_data_.Pass(); } float sample_rate() const OVERRIDE { return sample_rate_; } int32 number_of_frames() const OVERRIDE { return number_of_frames_; } int32 number_of_channels() const OVERRIDE { return number_of_channels_; } + SampleType sample_type() const OVERRIDE { return sample_type_; } private: - AudioFileReaderWAV(const uint8* data, size_t size); + AudioFileReaderWAV(const uint8* data, size_t size, SampleType sample_type); bool ParseRIFFHeader(const uint8* data, size_t size); void ParseChunks(const uint8* data, size_t size); @@ -49,6 +52,7 @@ float sample_rate_; int32 number_of_frames_; int32 number_of_channels_; + SampleType sample_type_; }; } // namespace audio
diff --git a/src/cobalt/audio/audio_helpers.h b/src/cobalt/audio/audio_helpers.h new file mode 100644 index 0000000..2f0c06a --- /dev/null +++ b/src/cobalt/audio/audio_helpers.h
@@ -0,0 +1,128 @@ +/* + * Copyright 2016 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_AUDIO_AUDIO_HELPERS_H_ +#define COBALT_AUDIO_AUDIO_HELPERS_H_ + +#include "media/base/shell_audio_bus.h" + +#if defined(OS_STARBOARD) +#include "starboard/audio_sink.h" +#include "starboard/media.h" +#endif + +namespace cobalt { +namespace audio { + +typedef ::media::ShellAudioBus::SampleType SampleType; +const SampleType kSampleTypeInt16 = ::media::ShellAudioBus::kInt16; +const SampleType kSampleTypeFloat32 = ::media::ShellAudioBus::kFloat32; + +const float kMaxInt16AsFloat32 = 32767.0f; + +#if defined(OS_STARBOARD) +// Get the size in bytes of an SbMediaAudioSampleType. +inline size_t GetStarboardSampleTypeSize(SbMediaAudioSampleType sample_type) { + switch (sample_type) { + case kSbMediaAudioSampleTypeFloat32: + return sizeof(float); + case kSbMediaAudioSampleTypeInt16: + return sizeof(int16); + } + NOTREACHED(); + return 0u; +} +#endif + +// Get the size in bytes of an internal sample type, which is an alias for +// media::ShellAudioBus::SampleType. +inline size_t GetSampleTypeSize(SampleType sample_type) { + switch (sample_type) { + case kSampleTypeInt16: + return sizeof(int16); + case kSampleTypeFloat32: + return sizeof(float); + } + NOTREACHED(); + return 0u; +} + +// Get the sample type that we would prefer to output in using starboard, as +// an internal SampleType. If we are not running on starboard or using the +// starboard media pipeline, then the preferred sample type is always float32. +inline SampleType GetPreferredOutputSampleType() { +#if defined(OS_STARBOARD) +#if SB_CAN(MEDIA_USE_STARBOARD_PIPELINE) + if (SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)) { + return kSampleTypeFloat32; + } + DCHECK(SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeInt16)) + << "At least one starboard audio sample type must be supported if using " + "starboard media pipeline."; + return kSampleTypeInt16; +#else // SB_CAN(MEDIA_USE_STARBOARD_PIPELINE) + return kSampleTypeFloat32; +#endif // SB_CAN(MEDIA_USE_STARBOARD_PIPELINE) +#else // defined(OS_STARBOARD) + return kSampleTypeFloat32; +#endif // defined(OS_STARBOARD) +} + +#if defined(OS_STARBOARD) +// The same as GetPreferredOutputSampleType, only as an SbMediaAudioSample +// rather than an internal SampleType. +inline SbMediaAudioSampleType GetPreferredOutputStarboardSampleType() { + if (SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)) { + return kSbMediaAudioSampleTypeFloat32; + } + return kSbMediaAudioSampleTypeInt16; +} +#endif + +// Convert a sample value from {int16,float} to {int16,float}. +template <typename SourceType, typename DestType> +inline DestType ConvertSample(SourceType sample); + +template <> +inline int16 ConvertSample<float, int16>(float sample) { + if (!(-1.0 <= sample && sample <= 1.0)) { + DLOG(WARNING) + << "Sample of type float32 must lie on interval [-1.0, 1.0], got: " + << sample << "."; + } + return static_cast<int16>(sample * kMaxInt16AsFloat32); +} + +template <> +inline float ConvertSample<int16, float>(int16 sample) { + float int16_sample_as_float = static_cast<float>(sample); + return int16_sample_as_float / kMaxInt16AsFloat32; +} + +template <> +inline float ConvertSample<float, float>(float sample) { + return sample; +} + +template <> +inline int16 ConvertSample<int16, int16>(int16 sample) { + return sample; +} + +} // namespace audio +} // namespace cobalt + +#endif // COBALT_AUDIO_AUDIO_HELPERS_H_
diff --git a/src/cobalt/audio/audio_node.h b/src/cobalt/audio/audio_node.h index 11f4ae4..01a578d 100644 --- a/src/cobalt/audio/audio_node.h +++ b/src/cobalt/audio/audio_node.h
@@ -20,6 +20,7 @@ #include <string> #include <vector> +#include "cobalt/audio/audio_helpers.h" #include "cobalt/audio/audio_node_input.h" #include "cobalt/audio/audio_node_output.h" #include "cobalt/dom/dom_exception.h" @@ -110,9 +111,12 @@ // Disconnects an AudioNode's output. void Disconnect(uint32 output, script::ExceptionState* exception_state); + // Called when a new input node has been connected. + virtual void OnInputNodeConnected() {} + // TODO: Support wrapping ShellAudioBus into another ShellAudioBus. virtual scoped_ptr<ShellAudioBus> PassAudioBusFromSource( - int32 number_of_frames) = 0; + int32 number_of_frames, SampleType sample_type) = 0; AudioLock* audio_lock() const { return audio_lock_.get(); }
diff --git a/src/cobalt/audio/audio_node_input.cc b/src/cobalt/audio/audio_node_input.cc index 2f42724..b7ac086 100644 --- a/src/cobalt/audio/audio_node_input.cc +++ b/src/cobalt/audio/audio_node_input.cc
@@ -197,6 +197,7 @@ output->AddInput(this); outputs_.insert(output); + owner_node_->OnInputNodeConnected(); } void AudioNodeInput::Disconnect(AudioNodeOutput* output) { @@ -232,13 +233,19 @@ // TODO: Consider computing computedNumberOfChannels and do up-mix or // down-mix base on computedNumberOfChannels. The current implementation // is based on the fact that the channelCountMode is max. - DCHECK_EQ(owner_node_->channel_count_mode(), AudioNode::kMax); + if (owner_node_->channel_count_mode() != AudioNode::kMax) { + DLOG(ERROR) << "Unsupported channel count mode: " + << owner_node_->channel_count_mode(); + return; + } + // Pull audio buffer from connected audio input. When an input is connected // from one or more AudioNode outputs. Fan-in is supported. for (std::set<AudioNodeOutput*>::iterator iter = outputs_.begin(); iter != outputs_.end(); ++iter) { scoped_ptr<ShellAudioBus> audio_bus = (*iter)->PassAudioBusFromSource( - static_cast<int32>(output_audio_bus->frames())); + static_cast<int32>(output_audio_bus->frames()), + output_audio_bus->sample_type()); if (audio_bus) { MixAudioBuffer(owner_node_->channel_interpretation(), audio_bus.get(),
diff --git a/src/cobalt/audio/audio_node_input_output_test.cc b/src/cobalt/audio/audio_node_input_output_test.cc index b72579e..095217f 100644 --- a/src/cobalt/audio/audio_node_input_output_test.cc +++ b/src/cobalt/audio/audio_node_input_output_test.cc
@@ -14,9 +14,9 @@ * limitations under the License. */ -#include "cobalt/audio/audio_context.h" - #include "cobalt/audio/audio_buffer_source_node.h" +#include "cobalt/audio/audio_context.h" +#include "cobalt/audio/audio_helpers.h" #include "testing/gtest/include/gtest/gtest.h" namespace cobalt { @@ -37,8 +37,8 @@ } // From AudioNode. - scoped_ptr<ShellAudioBus> PassAudioBusFromSource( - int32 /*number_of_frames*/) OVERRIDE { + scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32, /*number_of_frames*/ + SampleType) OVERRIDE { NOTREACHED(); return scoped_ptr<ShellAudioBus>(); } @@ -58,7 +58,8 @@ scoped_refptr<AudioBufferSourceNode> source(new AudioBufferSourceNode(NULL)); scoped_refptr<AudioBuffer> buffer( new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames), - static_cast<int32>(num_of_src_channel), src_data.Pass())); + static_cast<int32>(num_of_src_channel), src_data.Pass(), + GetPreferredOutputSampleType())); source->set_buffer(buffer); scoped_refptr<AudioDestinationNodeMock> destination( @@ -625,9 +626,10 @@ memcpy(src_buffer_1, src_data_in_float_1, 200 * sizeof(uint8)); scoped_refptr<AudioBufferSourceNode> source_1( new AudioBufferSourceNode(NULL)); - scoped_refptr<AudioBuffer> buffer_1(new AudioBuffer( - NULL, 44100, static_cast<int32>(num_of_frames_1), - static_cast<int32>(num_of_src_channel), src_data_1.Pass())); + scoped_refptr<AudioBuffer> buffer_1( + new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames_1), + static_cast<int32>(num_of_src_channel), src_data_1.Pass(), + GetPreferredOutputSampleType())); source_1->set_buffer(buffer_1); size_t num_of_frames_2 = 50; @@ -642,9 +644,10 @@ memcpy(src_buffer_2, src_data_in_float_2, 400 * sizeof(uint8)); scoped_refptr<AudioBufferSourceNode> source_2( new AudioBufferSourceNode(NULL)); - scoped_refptr<AudioBuffer> buffer_2(new AudioBuffer( - NULL, 44100, static_cast<int32>(num_of_frames_2), - static_cast<int32>(num_of_src_channel), src_data_2.Pass())); + scoped_refptr<AudioBuffer> buffer_2( + new AudioBuffer(NULL, 44100, static_cast<int32>(num_of_frames_2), + static_cast<int32>(num_of_src_channel), src_data_2.Pass(), + GetPreferredOutputSampleType())); source_2->set_buffer(buffer_2); scoped_refptr<AudioDestinationNodeMock> destination(
diff --git a/src/cobalt/audio/audio_node_output.cc b/src/cobalt/audio/audio_node_output.cc index d2ce36c..747bc01 100644 --- a/src/cobalt/audio/audio_node_output.cc +++ b/src/cobalt/audio/audio_node_output.cc
@@ -58,12 +58,13 @@ } scoped_ptr<ShellAudioBus> AudioNodeOutput::PassAudioBusFromSource( - int32 number_of_frames) { + int32 number_of_frames, SampleType sample_type) { // This is called by Audio thread. owner_node_->audio_lock()->AssertLocked(); // Pull audio buffer from its owner node. - return owner_node_->PassAudioBusFromSource(number_of_frames).Pass(); + return owner_node_->PassAudioBusFromSource(number_of_frames, sample_type) + .Pass(); } } // namespace audio
diff --git a/src/cobalt/audio/audio_node_output.h b/src/cobalt/audio/audio_node_output.h index f01b274..decd54c 100644 --- a/src/cobalt/audio/audio_node_output.h +++ b/src/cobalt/audio/audio_node_output.h
@@ -22,6 +22,7 @@ #include "base/memory/ref_counted.h" #include "cobalt/audio/audio_buffer.h" +#include "cobalt/audio/audio_helpers.h" #include "media/base/shell_audio_bus.h" namespace cobalt { @@ -44,7 +45,8 @@ void DisconnectAll(); - scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32 number_of_frames); + scoped_ptr<ShellAudioBus> PassAudioBusFromSource(int32 number_of_frames, + SampleType sample_type); private: AudioNode* const owner_node_;
diff --git a/src/cobalt/base/base.gyp b/src/cobalt/base/base.gyp index 19652ec..29ad3e2 100644 --- a/src/cobalt/base/base.gyp +++ b/src/cobalt/base/base.gyp
@@ -49,7 +49,6 @@ 'poller.h', 'polymorphic_downcast.h', 'polymorphic_equatable.h', - 'sanitizer_options.cc', 'source_location.h', 'stop_watch.cc', 'stop_watch.h',
diff --git a/src/cobalt/base/c_val.h b/src/cobalt/base/c_val.h index c4bcc0c..7331781 100644 --- a/src/cobalt/base/c_val.h +++ b/src/cobalt/base/c_val.h
@@ -249,6 +249,7 @@ {10LL * 1024LL * 1024LL * 1024LL, 1024LL * 1024LL * 1024LL, "GB"}, {10LL * 1024LL * 1024LL, 1024LL * 1024LL, "MB"}, {10LL * 1024LL, 1024LL, "KB"}, + {0LL, 1L, "B"}, }; return ThresholdList<cval::SizeInBytes>(thresholds, arraysize(thresholds)); } @@ -291,21 +292,23 @@ if (negative) { oss << "-"; } + + oss << std::fixed << std::setprecision(1) << std::setfill('0'); if (value_in_us > kHour) { - oss << value_in_us / kHour << ":" << std::setfill('0') << std::setw(2) - << (value_in_us % kHour) / kMinute << ":" << std::setfill('0') + oss << value_in_us / kHour << ":" << std::setw(2) + << (value_in_us % kHour) / kMinute << ":" << std::setw(2) << (value_in_us % kMinute) / kSecond << "h"; } else if (value_in_us > kMinute) { - oss << value_in_us / kMinute << ":" << std::setfill('0') << std::setw(2) + oss << value_in_us / kMinute << ":" << std::setw(2) << (value_in_us % kMinute) / kSecond << "m"; - } else if (value_in_us > kSecond * 10) { - oss << value_in_us / kSecond << "s"; - } else if (value_in_us > kMillisecond * 2) { - oss << value_in_us / kMillisecond << "ms"; - } else if (value_in_us > 0) { - oss << value_in_us << "us"; + } else if (value_in_us > kSecond) { + oss << std::setw(1) + << static_cast<double>(value_in_us) / kSecond << "s"; + } else if (value_in_us > kMillisecond) { + oss << std::setw(1) + << static_cast<double>(value_in_us) / kMillisecond << "ms"; } else { - oss << value_in_us; + oss << value_in_us << "us"; } return oss.str();
diff --git a/src/cobalt/base/c_val_test.cc b/src/cobalt/base/c_val_test.cc index 9751266..c1941d3 100644 --- a/src/cobalt/base/c_val_test.cc +++ b/src/cobalt/base/c_val_test.cc
@@ -16,6 +16,7 @@ #include <limits> +#include "base/time.h" #include "cobalt/base/c_val.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -64,7 +65,27 @@ base::CValManager* cvm = base::CValManager::GetInstance(); base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); EXPECT_TRUE(result); - EXPECT_EQ(*result, "4095MB"); + EXPECT_EQ(*result, "4294M"); +} + +TEST(CValTest, RegisterAndPrintU32Zero) { + const std::string cval_name = "32-bit unsigned int"; + const uint32_t cval_value = 0; + base::CVal<uint32_t> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "0"); +} + +TEST(CValTest, RegisterAndPrintU32K) { + const std::string cval_name = "32-bit unsigned int"; + const uint32_t cval_value = 50000; + base::CVal<uint32_t> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "50K"); } TEST(CValTest, RegisterAndPrintU64) { @@ -74,7 +95,7 @@ base::CValManager* cvm = base::CValManager::GetInstance(); base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); EXPECT_TRUE(result); - EXPECT_EQ(*result, "17592186044415MB"); + EXPECT_EQ(*result, "18446744073709M"); } TEST(CValTest, RegisterAndPrintS32) { @@ -97,6 +118,266 @@ EXPECT_EQ(*result, "-9223372036854775808"); } +TEST(CValTest, RegisterAndPrintSizeInBytesB) { + const std::string cval_name = "SizeInBytes"; + cval::SizeInBytes cval_value(500); + base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "500B"); +} + +TEST(CValTest, RegisterAndPrintSizeInBytesZero) { + const std::string cval_name = "SizeInBytes"; + cval::SizeInBytes cval_value(0); + base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "0B"); +} + +TEST(CValTest, RegisterAndPrintSizeInBytesKB) { + const std::string cval_name = "SizeInBytes"; + cval::SizeInBytes cval_value(50000UL); + base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "48KB"); +} + +TEST(CValTest, RegisterAndPrintSizeInBytesMB) { + const std::string cval_name = "SizeInBytes"; + cval::SizeInBytes cval_value(50000000UL); + base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "47MB"); +} + +TEST(CValTest, RegisterAndPrintSizeInBytesGB) { + const std::string cval_name = "SizeInBytes"; + cval::SizeInBytes cval_value(50000000000UL); + base::CVal<cval::SizeInBytes> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "46GB"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaMicroseconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(50)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "50us"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaZero) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(0)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "0us"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaMilliseconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(50)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "50.0ms"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaMillisecondsSingleDigit) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(5)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "5.0ms"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaMillisecondsFraction) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(5500)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "5.5ms"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaSeconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromSeconds(50)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "50.0s"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaSecondsSingleDigit) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromSeconds(5)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "5.0s"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaSecondsFraction) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(5500)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "5.5s"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaMinutes) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMinutes(50)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "50:00m"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaMinutesSingleDigit) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMinutes(5)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "5:00m"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaMinutesAndSeconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromSeconds(92)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "1:32m"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaHours) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromHours(50)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "50:00:00h"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaHoursSingleDigit) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromHours(5)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "5:00:00h"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndMinutes) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMinutes(92)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "1:32:00h"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndMinutesAndSeconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromSeconds(92 * 60 + 32)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "1:32:32h"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaHoursAndSeconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromSeconds(60 * 60 + 32)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "1:00:32h"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMicroseconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMicroseconds(-3)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "-3us"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMilliseconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMilliseconds(-3)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "-3.0ms"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaNegativeSeconds) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromSeconds(-3)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "-3.0s"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaNegativeMinutes) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromMinutes(-3)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "-3:00m"); +} + +TEST(CValTest, RegisterAndPrintTimeDeltaNegativeHours) { + const std::string cval_name = "TimeDelta"; + base::TimeDelta cval_value(base::TimeDelta::FromHours(-3)); + base::CVal<base::TimeDelta> cval(cval_name, cval_value, "Description."); + base::CValManager* cvm = base::CValManager::GetInstance(); + base::optional<std::string> result = cvm->GetValueAsPrettyString(cval_name); + EXPECT_TRUE(result); + EXPECT_EQ(*result, "-3:00:00h"); +} + TEST(CValTest, RegisterAndPrintFloat) { const std::string cval_name = "float"; const float cval_value = 3.14159f;
diff --git a/src/cobalt/base/console_commands.cc b/src/cobalt/base/console_commands.cc index eecf31a..73e4f1b 100644 --- a/src/cobalt/base/console_commands.cc +++ b/src/cobalt/base/console_commands.cc
@@ -50,10 +50,15 @@ const std::string& message) const { DCHECK_GT(channel.length(), size_t(0)); base::AutoLock auto_lock(lock_); - CommandHandlerMap::const_iterator iter = command_channel_map_.find(channel); - if (iter != command_channel_map_.end()) { + CommandHandlerMap::const_iterator iter = + command_channel_map_.lower_bound(channel); + bool handler_found = false; + while (iter != command_channel_map_.end() && iter->first == channel) { + handler_found = true; iter->second->callback().Run(message); - } else { + ++iter; + } + if (!handler_found) { DLOG(WARNING) << "No command handler registered for channel: " << channel; } } @@ -71,8 +76,9 @@ std::string ConsoleCommandManager::GetShortHelp( const std::string& channel) const { base::AutoLock auto_lock(lock_); - CommandHandlerMap::const_iterator iter = command_channel_map_.find(channel); - if (iter != command_channel_map_.end()) { + for (CommandHandlerMap::const_iterator iter = + command_channel_map_.lower_bound(channel); + iter != command_channel_map_.end() && iter->first == channel; ++iter) { return iter->second->short_help(); } return "No help available for unregistered channel: " + channel; @@ -81,8 +87,9 @@ std::string ConsoleCommandManager::GetLongHelp( const std::string& channel) const { base::AutoLock auto_lock(lock_); - CommandHandlerMap::const_iterator iter = command_channel_map_.find(channel); - if (iter != command_channel_map_.end()) { + for (CommandHandlerMap::const_iterator iter = + command_channel_map_.lower_bound(channel); + iter != command_channel_map_.end() && iter->first == channel; ++iter) { return iter->second->long_help(); } return "No help available for unregistered channel: " + channel; @@ -92,14 +99,22 @@ const CommandHandler* handler) { DCHECK_GT(handler->channel().length(), size_t(0)); base::AutoLock auto_lock(lock_); - command_channel_map_[handler->channel()] = handler; + command_channel_map_.insert(std::make_pair(handler->channel(), handler)); } void ConsoleCommandManager::UnregisterCommandHandler( const CommandHandler* handler) { - DCHECK_GT(handler->channel().length(), size_t(0)); + const std::string& channel = handler->channel(); + DCHECK_GT(channel.length(), size_t(0)); base::AutoLock auto_lock(lock_); - command_channel_map_.erase(handler->channel()); + for (CommandHandlerMap::iterator iter = + command_channel_map_.lower_bound(channel); + iter != command_channel_map_.end() && iter->first == channel; ++iter) { + if (iter->second == handler) { + command_channel_map_.erase(iter); + break; + } + } } #else // ENABLE_DEBUG_CONSOLE
diff --git a/src/cobalt/base/console_commands.h b/src/cobalt/base/console_commands.h index 47144d6..f12113e 100644 --- a/src/cobalt/base/console_commands.h +++ b/src/cobalt/base/console_commands.h
@@ -89,7 +89,7 @@ #if defined(ENABLE_DEBUG_CONSOLE) // Command handler map type. - typedef std::map<std::string, const CommandHandler*> CommandHandlerMap; + typedef std::multimap<std::string, const CommandHandler*> CommandHandlerMap; // Methods to register/unregister command handlers. // These are intended only to be called from the command handler objects.
diff --git a/src/cobalt/base/deep_link_event.h b/src/cobalt/base/deep_link_event.h index 5e3df4b..b5c2afa 100644 --- a/src/cobalt/base/deep_link_event.h +++ b/src/cobalt/base/deep_link_event.h
@@ -20,6 +20,7 @@ #include <string> #include "base/compiler_specific.h" +#include "base/string_util.h" #include "cobalt/base/event.h" namespace base { @@ -28,6 +29,9 @@ public: explicit DeepLinkEvent(const std::string& link) : link_(link) {} const std::string& link() const { return link_; } + bool IsH5vccLink() const { + return StartsWithASCII(link_, "h5vcc", true); + } BASE_EVENT_SUBCLASS(DeepLinkEvent);
diff --git a/src/cobalt/base/sanitizer_options.cc b/src/cobalt/base/sanitizer_options.cc deleted file mode 100644 index b5311a5..0000000 --- a/src/cobalt/base/sanitizer_options.cc +++ /dev/null
@@ -1,47 +0,0 @@ -/* - * Copyright 2015 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. - */ - -#if defined(ADDRESS_SANITIZER) - -// Functions returning default options are declared weak in the tools' runtime -// libraries. To make the linker pick the strong replacements for those -// functions from this module, we explicitly force its inclusion by passing -// -Wl,-u_sanitizer_options_link_helper -extern "C" void _sanitizer_options_link_helper() { } - -// The callbacks we define here will be called from the sanitizer runtime, but -// aren't referenced from the executable. We must ensure that those -// callbacks are not sanitizer-instrumented, and that they aren't stripped by -// the linker. -#define SANITIZER_HOOK_ATTRIBUTE \ - extern "C" \ - __attribute__((no_sanitize_address)) \ - __attribute__((no_sanitize_memory)) \ - __attribute__((no_sanitize_thread)) \ - __attribute__((visibility("default"))) \ - __attribute__((used)) - -extern "C" char kLSanDefaultSuppressions[]; - -char kLSanDefaultSuppressions[] = - // 16-byte leak from a call to calloc() in this library. - "leak:egl_gallium.so\n"; - -SANITIZER_HOOK_ATTRIBUTE const char *__lsan_default_suppressions() { - return kLSanDefaultSuppressions; -} - -#endif // defined(ADDRESS_SANITIZER)
diff --git a/src/cobalt/base/tokens.h b/src/cobalt/base/tokens.h index b2e24cd..cae31dd 100644 --- a/src/cobalt/base/tokens.h +++ b/src/cobalt/base/tokens.h
@@ -83,6 +83,7 @@ MacroOpWithNameAndValue(class_selector_prefix, ".") \ MacroOpWithNameAndValue(comment_node_name, "#comment") \ MacroOpWithNameAndValue(document_name, "#document") \ + MacroOpWithNameAndValue(domcontentloaded, "DOMContentLoaded") \ MacroOpWithNameAndValue(empty_pseudo_class_selector, "empty") \ MacroOpWithNameAndValue(focus_pseudo_class_selector, "focus") \ MacroOpWithNameAndValue(hover_pseudo_class_selector, "hover") \
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc index be7915c..9a486e9 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousIndexedGetterInterface.cc
@@ -280,6 +280,15 @@ JSBool get_length( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<AnonymousIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc index 388c186..0d132c4 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsAnonymousNamedIndexedGetterInterface.cc
@@ -371,6 +371,15 @@ JSBool get_length( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<AnonymousNamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc index 71b31f2..38699b2 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsArbitraryInterface.cc
@@ -191,6 +191,15 @@ JSBool get_arbitraryProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ArbitraryInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -213,6 +222,15 @@ JSBool set_arbitraryProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ArbitraryInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -247,6 +265,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ArbitraryInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsBaseInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsBaseInterface.cc index bf4b5a3..c6f9524 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsBaseInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsBaseInterface.cc
@@ -191,6 +191,15 @@ JSBool get_baseAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<BaseInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -225,6 +234,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<BaseInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc index 1796c62..2599232 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsBooleanTypeTestInterface.cc
@@ -189,6 +189,15 @@ JSBool get_booleanProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<BooleanTypeTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -211,6 +220,15 @@ JSBool set_booleanProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<BooleanTypeTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -245,6 +263,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<BooleanTypeTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -291,6 +318,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<BooleanTypeTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc index 0b7e907..fce394f 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackFunctionInterface.cc
@@ -193,6 +193,15 @@ JSBool get_callbackAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -215,6 +224,15 @@ JSBool set_callbackAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -237,6 +255,15 @@ JSBool get_nullableCallbackAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -259,6 +286,15 @@ JSBool set_nullableCallbackAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -293,6 +329,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -339,6 +384,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -385,6 +439,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -431,6 +494,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -477,6 +549,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackFunctionInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc index d4321ff..468faf3 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsCallbackInterfaceInterface.cc
@@ -193,6 +193,15 @@ JSBool get_callbackAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackInterfaceInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -215,6 +224,15 @@ JSBool set_callbackAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackInterfaceInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -249,6 +267,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackInterfaceInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -295,6 +322,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<CallbackInterfaceInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc index e8e5a7d..2610e00 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsConditionalInterface.cc
@@ -192,6 +192,15 @@ JSBool get_enabledAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConditionalInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -214,6 +223,15 @@ JSBool set_enabledAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConditionalInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -238,6 +256,15 @@ JSBool get_disabledAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConditionalInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -260,6 +287,15 @@ JSBool set_disabledAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConditionalInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -296,6 +332,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConditionalInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -326,6 +371,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConditionalInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsConstructorWithArgumentsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsConstructorWithArgumentsInterface.cc index 09ccf2a..756c696 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsConstructorWithArgumentsInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsConstructorWithArgumentsInterface.cc
@@ -191,6 +191,15 @@ JSBool get_longArg( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConstructorWithArgumentsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -213,6 +222,15 @@ JSBool get_booleanArg( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConstructorWithArgumentsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -235,6 +253,15 @@ JSBool get_stringArg( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ConstructorWithArgumentsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc index 7fb410b..7092da5 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDOMStringTestInterface.cc
@@ -189,6 +189,15 @@ JSBool get_property( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -211,6 +220,15 @@ JSBool set_property( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -233,6 +251,15 @@ JSBool get_readOnlyProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -255,6 +282,15 @@ JSBool get_readOnlyTokenProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -277,6 +313,15 @@ JSBool get_nullIsEmptyProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -299,6 +344,15 @@ JSBool set_nullIsEmptyProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -321,6 +375,15 @@ JSBool get_undefinedIsEmptyProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -343,6 +406,15 @@ JSBool set_undefinedIsEmptyProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -365,6 +437,15 @@ JSBool get_nullableUndefinedIsEmptyProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -387,6 +468,15 @@ JSBool set_nullableUndefinedIsEmptyProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DOMStringTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc index 9d7f281..7c48cfc 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedGetterSetterInterface.cc
@@ -371,6 +371,15 @@ JSBool get_length( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DerivedGetterSetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -393,6 +402,15 @@ JSBool get_propertyOnDerivedClass( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DerivedGetterSetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -415,6 +433,15 @@ JSBool set_propertyOnDerivedClass( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DerivedGetterSetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -449,6 +476,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DerivedGetterSetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -501,6 +537,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DerivedGetterSetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -559,6 +604,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DerivedGetterSetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedInterface.cc index 8d64be2..5e26084 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDerivedInterface.cc
@@ -191,6 +191,15 @@ JSBool get_derivedAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DerivedInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -225,6 +234,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DerivedInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc index 3f3d4a7..5e3f995 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsDisabledInterface.cc
@@ -191,6 +191,15 @@ JSBool get_disabledProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DisabledInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -213,6 +222,15 @@ JSBool set_disabledProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DisabledInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -247,6 +265,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<DisabledInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc index c54bde2..38c99f1 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsEnumerationInterface.cc
@@ -200,6 +200,15 @@ JSBool get_enumProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<EnumerationInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -222,6 +231,15 @@ JSBool set_enumProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<EnumerationInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionObjectInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionObjectInterface.cc index 64e3ca6..d9f10ef 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionObjectInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionObjectInterface.cc
@@ -190,6 +190,15 @@ JSBool get_error( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ExceptionObjectInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -212,6 +221,15 @@ JSBool get_message( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ExceptionObjectInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc index 5f1a396..6ab9aab 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsExceptionsInterface.cc
@@ -191,6 +191,15 @@ JSBool get_attributeThrowsException( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ExceptionsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -213,6 +222,15 @@ JSBool set_attributeThrowsException( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ExceptionsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -247,6 +265,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ExceptionsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsExtendedIDLAttributesInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsExtendedIDLAttributesInterface.cc index 07cceda..e197c87 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsExtendedIDLAttributesInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsExtendedIDLAttributesInterface.cc
@@ -201,6 +201,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ExtendedIDLAttributesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -208,10 +217,10 @@ WrapperPrivate::GetFromObject(context, object); ExtendedIDLAttributesInterface* impl = wrapper_private->wrappable<ExtendedIDLAttributesInterface>().get(); - MozjsGlobalEnvironment* global_environment = + MozjsGlobalEnvironment* callwith_global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); - impl->CallWithSettings(global_environment->GetEnvironmentSettings()); + impl->CallWithSettings(callwith_global_environment->GetEnvironmentSettings()); result_value.set(JS::UndefinedHandleValue); return !exception_state.is_exception_set(); }
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc index d2cfb40..9ba778e 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsGarbageCollectionTestInterface.cc
@@ -202,6 +202,15 @@ JSBool get_previous( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<GarbageCollectionTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -224,6 +233,15 @@ JSBool set_previous( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<GarbageCollectionTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -246,6 +264,15 @@ JSBool get_next( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<GarbageCollectionTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -268,6 +295,15 @@ JSBool set_next( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<GarbageCollectionTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsGlobalInterfaceParent.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsGlobalInterfaceParent.cc index d742500..a0ddac4 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsGlobalInterfaceParent.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsGlobalInterfaceParent.cc
@@ -201,6 +201,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<GlobalInterfaceParent>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc index 752334e..66f6497 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsIndexedGetterInterface.cc
@@ -295,6 +295,15 @@ JSBool get_length( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<IndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -329,6 +338,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<IndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -375,6 +393,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<IndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -427,6 +454,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<IndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsInterfaceWithUnsupportedProperties.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsInterfaceWithUnsupportedProperties.cc index a578e9c..56ad080 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsInterfaceWithUnsupportedProperties.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsInterfaceWithUnsupportedProperties.cc
@@ -189,6 +189,15 @@ JSBool get_supportedAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<InterfaceWithUnsupportedProperties>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc index 918f252..51f1f54 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedGetterInterface.cc
@@ -307,6 +307,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -353,6 +362,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -405,6 +423,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc index 2965f0f..2715fd9 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNamedIndexedGetterInterface.cc
@@ -371,6 +371,15 @@ JSBool get_length( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -393,6 +402,15 @@ JSBool get_propertyOnBaseClass( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -415,6 +433,15 @@ JSBool set_propertyOnBaseClass( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -449,6 +476,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -501,6 +537,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -559,6 +604,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -611,6 +665,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -669,6 +732,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NamedIndexedGetterInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc index 69fab04..9b732fe 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNestedPutForwardsInterface.cc
@@ -193,6 +193,15 @@ JSBool get_nestedForwardingAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NestedPutForwardsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -215,6 +224,15 @@ JSBool set_nestedForwardingAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NestedPutForwardsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc index c0010f4..e7533d8 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNullableTypesTestInterface.cc
@@ -193,6 +193,15 @@ JSBool get_nullableBooleanProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -215,6 +224,15 @@ JSBool set_nullableBooleanProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -237,6 +255,15 @@ JSBool get_nullableNumericProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -259,6 +286,15 @@ JSBool set_nullableNumericProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -281,6 +317,15 @@ JSBool get_nullableStringProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -303,6 +348,15 @@ JSBool set_nullableStringProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -325,6 +379,15 @@ JSBool get_nullableObjectProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -347,6 +410,15 @@ JSBool set_nullableObjectProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -381,6 +453,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -427,6 +508,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -461,6 +551,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -507,6 +606,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -541,6 +649,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -587,6 +704,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -621,6 +747,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -667,6 +802,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NullableTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc index 4bf1c6e..eb604d8 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsNumericTypesTestInterface.cc
@@ -189,6 +189,15 @@ JSBool get_byteProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -211,6 +220,15 @@ JSBool set_byteProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -233,6 +251,15 @@ JSBool get_octetProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -255,6 +282,15 @@ JSBool set_octetProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -277,6 +313,15 @@ JSBool get_shortProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -299,6 +344,15 @@ JSBool set_shortProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -321,6 +375,15 @@ JSBool get_unsignedShortProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -343,6 +406,15 @@ JSBool set_unsignedShortProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -365,6 +437,15 @@ JSBool get_longProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -387,6 +468,15 @@ JSBool set_longProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -409,6 +499,15 @@ JSBool get_unsignedLongProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -431,6 +530,15 @@ JSBool set_unsignedLongProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -453,6 +561,15 @@ JSBool get_longLongProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -475,6 +592,15 @@ JSBool set_longLongProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -497,6 +623,15 @@ JSBool get_unsignedLongLongProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -519,6 +654,15 @@ JSBool set_unsignedLongLongProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -541,6 +685,15 @@ JSBool get_doubleProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -563,6 +716,15 @@ JSBool set_doubleProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -585,6 +747,15 @@ JSBool get_unrestrictedDoubleProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -607,6 +778,15 @@ JSBool set_unrestrictedDoubleProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -641,6 +821,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -687,6 +876,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -721,6 +919,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -767,6 +974,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -801,6 +1017,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -847,6 +1072,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -893,6 +1127,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -927,6 +1170,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -961,6 +1213,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1007,6 +1268,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1041,6 +1311,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1087,6 +1366,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1121,6 +1409,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1167,6 +1464,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1201,6 +1507,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1247,6 +1562,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1293,6 +1617,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1327,6 +1660,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1361,6 +1703,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1407,6 +1758,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<NumericTypesTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc index 1b20995..77631f6 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsObjectTypeBindingsInterface.cc
@@ -201,6 +201,15 @@ JSBool get_arbitraryObject( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ObjectTypeBindingsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -223,6 +232,15 @@ JSBool set_arbitraryObject( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ObjectTypeBindingsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -245,6 +263,15 @@ JSBool get_baseInterface( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ObjectTypeBindingsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -267,6 +294,15 @@ JSBool get_derivedInterface( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ObjectTypeBindingsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -289,6 +325,15 @@ JSBool set_derivedInterface( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ObjectTypeBindingsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -311,6 +356,15 @@ JSBool get_objectProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ObjectTypeBindingsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -333,6 +387,15 @@ JSBool set_objectProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<ObjectTypeBindingsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsOperationsTestInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsOperationsTestInterface.cc index b30eecb..293e7cd 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsOperationsTestInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsOperationsTestInterface.cc
@@ -205,6 +205,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -239,6 +248,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -273,6 +291,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -317,6 +344,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -415,6 +451,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -473,6 +518,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -501,6 +555,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -547,6 +610,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -593,6 +665,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -663,6 +744,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -738,6 +828,10 @@ MozjsGlobalEnvironment* global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + JS::RootedObject object(context); + if (arg.isObject()) { + object = JSVAL_TO_OBJECT(arg); + } if (arg.isNumber()) { return fcn_overloadedFunction2( context, argc, vp); @@ -759,8 +853,12 @@ MozjsGlobalEnvironment* global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + JS::RootedObject object(context); + if (arg.isObject()) { + object = JSVAL_TO_OBJECT(arg); + } if (arg.isObject() ? wrapper_factory->DoesObjectImplementInterface( - JSVAL_TO_OBJECT(arg), base::GetTypeId<ArbitraryInterface>()) : + object, base::GetTypeId<ArbitraryInterface>()) : false) { return fcn_overloadedFunction5( context, argc, vp); @@ -795,6 +893,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -841,6 +948,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -883,6 +999,10 @@ MozjsGlobalEnvironment* global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + JS::RootedObject object(context); + if (arg.isObject()) { + object = JSVAL_TO_OBJECT(arg); + } if (arg.isNullOrUndefined()) { return fcn_overloadedNullable2( context, argc, vp); @@ -917,6 +1037,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -951,6 +1080,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -999,6 +1137,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1085,6 +1232,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1131,6 +1287,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1159,6 +1324,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -1205,6 +1379,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<OperationsTestInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc index 870e3a3..dfc7ffa 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsPutForwardsInterface.cc
@@ -193,6 +193,15 @@ JSBool get_forwardingAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<PutForwardsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -215,6 +224,15 @@ JSBool set_forwardingAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<PutForwardsInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsSingleOperationInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsSingleOperationInterface.cc index aaf933d..36e73dd 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsSingleOperationInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsSingleOperationInterface.cc
@@ -27,6 +27,7 @@ #include "cobalt/script/logging_exception_state.h" #include "cobalt/script/mozjs/conversion_helpers.h" #include "cobalt/script/mozjs/mozjs_callback_interface.h" +#include "cobalt/script/mozjs/util/exception_helpers.h" #include "third_party/mozjs/js/src/jsapi.h" #include "third_party/mozjs/js/src/jscntxt.h" @@ -40,6 +41,7 @@ using cobalt::script::mozjs::FromJSValue; using cobalt::script::mozjs::GetCallableForCallbackInterface; using cobalt::script::mozjs::ToJSValue; +using cobalt::script::mozjs::util::GetExceptionString; } // namespace namespace cobalt { @@ -58,13 +60,13 @@ bool* had_exception) const { bool success = false; base::optional<int32_t > cobalt_return_value; + JSAutoRequest auto_request(context_); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); // This could be set to NULL if it was garbage collected. JS::RootedObject implementing_object(context_, implementing_object_.Get()); DLOG_IF(WARNING, !implementing_object) << "Implementing object is NULL."; if (implementing_object) { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, implementing_object); // Get callable object. @@ -90,7 +92,8 @@ DCHECK(function); success = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); - DLOG_IF(WARNING, !success) << "Exception in callback."; + DLOG_IF(WARNING, !success) << "Exception in callback: " + << GetExceptionString(context_); if (success) { LoggingExceptionState exception_state; FromJSValue(context_, return_value, 0, &exception_state,
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsStaticPropertiesInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsStaticPropertiesInterface.cc index 02ad356..82a5868 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsStaticPropertiesInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsStaticPropertiesInterface.cc
@@ -425,6 +425,10 @@ MozjsGlobalEnvironment* global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + JS::RootedObject object(context); + if (arg.isObject()) { + object = JSVAL_TO_OBJECT(arg); + } if (arg.isNumber()) { return staticfcn_staticFunction2( context, argc, vp); @@ -446,8 +450,12 @@ MozjsGlobalEnvironment* global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + JS::RootedObject object(context); + if (arg.isObject()) { + object = JSVAL_TO_OBJECT(arg); + } if (arg.isObject() ? wrapper_factory->DoesObjectImplementInterface( - JSVAL_TO_OBJECT(arg), base::GetTypeId<ArbitraryInterface>()) : + object, base::GetTypeId<ArbitraryInterface>()) : false) { return staticfcn_staticFunction5( context, argc, vp);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc index b1b8349..428506c 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierAttributeInterface.cc
@@ -189,6 +189,15 @@ JSBool get_theStringifierAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<StringifierAttributeInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -211,6 +220,15 @@ JSBool set_theStringifierAttribute( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<StringifierAttributeInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierOperationInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierOperationInterface.cc index 6b6dd67..8d33990 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierOperationInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsStringifierOperationInterface.cc
@@ -201,6 +201,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<StringifierOperationInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsTargetInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsTargetInterface.cc index 644db8f..82de15a 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsTargetInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsTargetInterface.cc
@@ -201,6 +201,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<TargetInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -229,6 +238,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<TargetInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc index 5c9b31f..e6cf73b 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsUnionTypesInterface.cc
@@ -197,6 +197,15 @@ JSBool get_unionProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<UnionTypesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -219,6 +228,15 @@ JSBool set_unionProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<UnionTypesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -241,6 +259,15 @@ JSBool get_unionWithNullableMemberProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<UnionTypesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -263,6 +290,15 @@ JSBool set_unionWithNullableMemberProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<UnionTypesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -285,6 +321,15 @@ JSBool get_nullableUnionProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<UnionTypesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -307,6 +352,15 @@ JSBool set_nullableUnionProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<UnionTypesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -329,6 +383,15 @@ JSBool get_unionBaseProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<UnionTypesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -351,6 +414,15 @@ JSBool set_unionBaseProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<UnionTypesInterface>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc b/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc index 0a5ddd1..212ecca 100644 --- a/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc +++ b/src/cobalt/bindings/generated/mozjs/testing/MozjsWindow.cc
@@ -382,6 +382,15 @@ JSBool get_windowProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<Window>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -404,6 +413,15 @@ JSBool set_windowProperty( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<Window>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -426,6 +444,15 @@ JSBool get_window( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<Window>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -460,6 +487,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<Window>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context); @@ -467,12 +503,12 @@ WrapperPrivate::GetFromObject(context, object); Window* impl = wrapper_private->wrappable<Window>().get(); - MozjsGlobalEnvironment* global_environment = + MozjsGlobalEnvironment* callwith_global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); if (!exception_state.is_exception_set()) { ToJSValue(context, - impl->GetStackTrace(global_environment->GetStackTrace()), + impl->GetStackTrace(callwith_global_environment->GetStackTrace()), &result_value); } if (!exception_state.is_exception_set()) { @@ -496,6 +532,15 @@ NOTREACHED(); return false; } + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<Window>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } MozjsExceptionState exception_state(context); JS::RootedValue result_value(context);
diff --git a/src/cobalt/bindings/mozjs/code_generator.py b/src/cobalt/bindings/mozjs/code_generator.py index 12ac5fb..c99c10a 100644 --- a/src/cobalt/bindings/mozjs/code_generator.py +++ b/src/cobalt/bindings/mozjs/code_generator.py
@@ -42,8 +42,8 @@ def inherits_interface(self, interface_name, arg): return ('%s.isObject() ?' ' wrapper_factory->DoesObjectImplementInterface(\n' - ' JSVAL_TO_OBJECT(%s), base::GetTypeId<%s>()) :\n' - ' false') % (arg, arg, interface_name) + ' object, base::GetTypeId<%s>()) :\n' + ' false') % (arg, interface_name) def is_number(self, arg): return '%s.isNumber()' % arg
diff --git a/src/cobalt/bindings/mozjs/templates/callback-interface.cc.template b/src/cobalt/bindings/mozjs/templates/callback-interface.cc.template index f307bbc..895e453 100644 --- a/src/cobalt/bindings/mozjs/templates/callback-interface.cc.template +++ b/src/cobalt/bindings/mozjs/templates/callback-interface.cc.template
@@ -20,6 +20,7 @@ #include "cobalt/script/logging_exception_state.h" #include "cobalt/script/mozjs/conversion_helpers.h" #include "cobalt/script/mozjs/mozjs_callback_interface.h" +#include "cobalt/script/mozjs/util/exception_helpers.h" #include "third_party/mozjs/js/src/jsapi.h" #include "third_party/mozjs/js/src/jscntxt.h" {% endblock includes %} @@ -30,6 +31,7 @@ using cobalt::script::mozjs::FromJSValue; using cobalt::script::mozjs::GetCallableForCallbackInterface; using cobalt::script::mozjs::ToJSValue; +using cobalt::script::mozjs::util::GetExceptionString; {% endblock using_directives %} {% block implementation %} @@ -56,13 +58,13 @@ {% if overload.type != 'void' %} {{overload.type}} cobalt_return_value; {% endif %} + JSAutoRequest auto_request(context_); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); // This could be set to NULL if it was garbage collected. JS::RootedObject implementing_object(context_, implementing_object_.Get()); DLOG_IF(WARNING, !implementing_object) << "Implementing object is NULL."; if (implementing_object) { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, implementing_object); // Get callable object. @@ -90,7 +92,8 @@ DCHECK(function); success = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); - DLOG_IF(WARNING, !success) << "Exception in callback."; + DLOG_IF(WARNING, !success) << "Exception in callback: " + << GetExceptionString(context_); {% if overload.type != 'void' %} if (success) { LoggingExceptionState exception_state;
diff --git a/src/cobalt/bindings/mozjs/templates/interface.cc.template b/src/cobalt/bindings/mozjs/templates/interface.cc.template index eb4e553..b92a526 100644 --- a/src/cobalt/bindings/mozjs/templates/interface.cc.template +++ b/src/cobalt/bindings/mozjs/templates/interface.cc.template
@@ -15,6 +15,7 @@ #} {% from 'macros.cc.template' import add_extra_arguments %} {% from 'macros.cc.template' import call_cobalt_function %} +{% from 'macros.cc.template' import check_if_object_implements_interface with context %} {% from 'macros.cc.template' import constructor_implementation with context %} {% from 'macros.cc.template' import function_implementation with context %} {% from 'macros.cc.template' import get_impl_class_instance %} @@ -458,6 +459,7 @@ JSBool get_{{attribute.idl_name}}( JSContext* context, JS::HandleObject object, JS::HandleId id, JS::MutableHandleValue vp) { +{{ check_if_object_implements_interface() }} {{ nonstatic_function_prologue(impl_class) }} {% endif %} {{ call_cobalt_function(impl_class, attribute.type, @@ -480,6 +482,7 @@ JSBool set_{{attribute.idl_name}}( JSContext* context, JS::HandleObject object, JS::HandleId id, JSBool strict, JS::MutableHandleValue vp) { +{{ check_if_object_implements_interface() }} {{ nonstatic_function_prologue(impl_class)}} {% endif %} {#- attribute.is_static #} {{ set_attribute_implementation(attribute, impl_class) -}}
diff --git a/src/cobalt/bindings/mozjs/templates/macros.cc.template b/src/cobalt/bindings/mozjs/templates/macros.cc.template index f9153c7..58b5669 100644 --- a/src/cobalt/bindings/mozjs/templates/macros.cc.template +++ b/src/cobalt/bindings/mozjs/templates/macros.cc.template
@@ -15,6 +15,21 @@ #} {# + # Checks if object implements interface. + #} +{% macro check_if_object_implements_interface() %} + MozjsGlobalEnvironment* global_environment = + static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); + WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + if (!wrapper_factory->DoesObjectImplementInterface( + object, base::GetTypeId<{{impl_class}}>())) { + MozjsExceptionState exception(context); + exception.SetSimpleException(script::kDoesNotImplementInterface); + return false; + } +{%- endmacro %} + +{# # Function body for operation bindings. # Parameters: # operation: The operation context object @@ -36,6 +51,7 @@ NOTREACHED(); return false; } +{{ check_if_object_implements_interface() }} {{ nonstatic_function_prologue(impl_class) }} {% endif %} {% call(arguments_list) extract_arguments(operation) %} @@ -256,10 +272,10 @@ #} {% macro add_extra_arguments(arguments_list, raises_exception, call_with) %} {% if call_with %} - MozjsGlobalEnvironment* global_environment = + MozjsGlobalEnvironment* callwith_global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); -{% do arguments_list.insert(0, - 'global_environment->Get%s()'|format(call_with)) %} +{% do arguments_list.insert( + 0, 'callwith_global_environment->Get%s()'|format(call_with)) %} {% endif %} {% do arguments_list.append('&exception_state') if raises_exception %} {%- endmacro %} @@ -372,6 +388,10 @@ MozjsGlobalEnvironment* global_environment = static_cast<MozjsGlobalEnvironment*>(JS_GetContextPrivate(context)); WrapperFactory* wrapper_factory = global_environment->wrapper_factory(); + JS::RootedObject object(context); + if (arg.isObject()) { + object = JSVAL_TO_OBJECT(arg); + } {% endif %} {% for test, overload in resolution_tests %} if ({{test("arg")}}) {
diff --git a/src/cobalt/bindings/testing/bindings_test_base.h b/src/cobalt/bindings/testing/bindings_test_base.h index 25e9ea2..f634699 100644 --- a/src/cobalt/bindings/testing/bindings_test_base.h +++ b/src/cobalt/bindings/testing/bindings_test_base.h
@@ -25,6 +25,7 @@ #include "cobalt/script/global_environment.h" #include "cobalt/script/javascript_engine.h" #include "cobalt/script/source_code.h" +#include "cobalt/script/wrappable.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -71,6 +72,17 @@ return global_environment_->EvaluateScript(source, out_result); } + bool EvaluateScript(const std::string& script, + const scoped_refptr<script::Wrappable>& owning_object, + base::optional<script::OpaqueHandleHolder::Reference>* + out_opaque_handle = NULL) { + scoped_refptr<script::SourceCode> source = + script::SourceCode::CreateSourceCode( + script, base::SourceLocation("[object BindingsTestBase]", 1, 1)); + return global_environment_->EvaluateScript(source, owning_object, + out_opaque_handle); + } + void CollectGarbage() { engine_->CollectGarbage(); } protected:
diff --git a/src/cobalt/bindings/testing/evaluate_script_test.cc b/src/cobalt/bindings/testing/evaluate_script_test.cc new file mode 100644 index 0000000..61d74fa --- /dev/null +++ b/src/cobalt/bindings/testing/evaluate_script_test.cc
@@ -0,0 +1,87 @@ +/* + * Copyright 2016 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/bindings/testing/arbitrary_interface.h" +#include "cobalt/bindings/testing/bindings_test_base.h" +#include "cobalt/bindings/testing/object_type_bindings_interface.h" +#include "cobalt/bindings/testing/script_object_owner.h" + +using cobalt::script::OpaqueHandleHolder; +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::StrictMock; + +namespace cobalt { +namespace bindings { +namespace testing { + +namespace { +class EvaluateScriptTest + : public InterfaceBindingsTest<ObjectTypeBindingsInterface> {}; +} // namespace + +TEST_F(EvaluateScriptTest, TwoArguments) { + std::string result; + + std::string script = + "function fib(n) {\n" + " if (n <= 0) {\n" + " return 0;\n" + " } else if (n == 1) {\n" + " return 1;\n" + " } else {\n" + " return fib(n - 1) + fib(n - 2);\n" + " }\n" + "}\n" + "fib(8)"; + EXPECT_TRUE(EvaluateScript(script, &result)); + EXPECT_STREQ("21", result.c_str()); +} + +TEST_F(EvaluateScriptTest, ThreeArguments) { + std::string script = + "function fib(n) {\n" + " if (n <= 0) {\n" + " return 0;\n" + " } else if (n == 1) {\n" + " return 1;\n" + " } else {\n" + " return fib(n - 1) + fib(n - 2);\n" + " }\n" + "}\n" + "fib(8)"; + + // Call with null out handle. + scoped_refptr<StrictMock<ArbitraryInterface> > arbitrary_interface_mock( + new StrictMock<ArbitraryInterface>()); + EXPECT_TRUE(EvaluateScript(script, arbitrary_interface_mock, NULL)); + + // Call with non-null, but unset optional handle. + base::optional<OpaqueHandleHolder::Reference> opaque_handle; + EXPECT_TRUE(EvaluateScript(script, arbitrary_interface_mock, &opaque_handle)); + ASSERT_FALSE(opaque_handle->referenced_object().IsNull()); + + EXPECT_CALL(test_mock(), object_property()) + .WillOnce(Return(&opaque_handle->referenced_object())); + std::string result; + EXPECT_TRUE(EvaluateScript("test.objectProperty == 21;", &result)); + EXPECT_STREQ("true", result.c_str()); +} + +} // namespace testing +} // namespace bindings +} // namespace cobalt
diff --git a/src/cobalt/bindings/testing/object_type_bindings_test.cc b/src/cobalt/bindings/testing/object_type_bindings_test.cc index 7c5cbf0..e80d859 100644 --- a/src/cobalt/bindings/testing/object_type_bindings_test.cc +++ b/src/cobalt/bindings/testing/object_type_bindings_test.cc
@@ -273,6 +273,18 @@ EXPECT_THAT(result.c_str(), StartsWith("TypeError:")); } +TEST_F(UserObjectBindingsTest, CallWrongObjectType) { + std::string result; + EXPECT_TRUE(EvaluateScript("var obj = new Object()", NULL)); + EXPECT_TRUE(EvaluateScript("var arb = new ArbitraryInterface()", NULL)); + + // Calling a function with the wrong object type is a type error. + EXPECT_FALSE( + EvaluateScript("obj.arbitraryFunction = arb.arbitraryFunction;\n" + "obj.arbitraryFunction();", &result)); + EXPECT_THAT(result.c_str(), StartsWith("TypeError:")); +} + } // namespace testing } // namespace bindings } // namespace cobalt
diff --git a/src/cobalt/bindings/testing/testing.gyp b/src/cobalt/bindings/testing/testing.gyp index 0286dcd..e9f6492 100644 --- a/src/cobalt/bindings/testing/testing.gyp +++ b/src/cobalt/bindings/testing/testing.gyp
@@ -129,6 +129,7 @@ 'dependent_interface_test.cc', 'dom_string_bindings_test.cc', 'enumeration_bindings_test.cc', + 'evaluate_script_test.cc', 'exceptions_bindings_test.cc', 'extended_attributes_test.cc', 'garbage_collection_test.cc',
diff --git a/src/cobalt/browser/application.cc b/src/cobalt/browser/application.cc index 72e730e..470eda6 100644 --- a/src/cobalt/browser/application.cc +++ b/src/cobalt/browser/application.cc
@@ -20,6 +20,7 @@ #include <vector> #include "base/command_line.h" +#include "base/debug/trace_event.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/path_service.h" @@ -50,18 +51,34 @@ #endif // defined(__LB_SHELL__FOR_RELEASE__) #include "lbshell/src/lb_memory_pages.h" #endif // defined(__LB_SHELL__) +#if defined(OS_STARBOARD) +#include "nb/analytics/memory_tracker.h" +#include "nb/analytics/memory_tracker_impl.h" +#include "starboard/configuration.h" +#include "starboard/log.h" +#endif // defined(OS_STARBOARD) namespace cobalt { namespace browser { namespace { const int kStatUpdatePeriodMs = 1000; +#if defined(COBALT_BUILD_TYPE_GOLD) +const int kLiteStatUpdatePeriodMs = 1000; +#else +const int kLiteStatUpdatePeriodMs = 16; +#endif const char kDefaultURL[] = "https://www.youtube.com/tv"; #if defined(ENABLE_REMOTE_DEBUGGING) int GetRemoteDebuggingPort() { +#if defined(SB_OVERRIDE_DEFAULT_REMOTE_DEBUGGING_PORT) + const int kDefaultRemoteDebuggingPort = + SB_OVERRIDE_DEFAULT_REMOTE_DEBUGGING_PORT; +#else const int kDefaultRemoteDebuggingPort = 9222; +#endif // defined(SB_OVERRIDE_DEFAULT_REMOTE_DEBUGGING_PORT) int remote_debugging_port = kDefaultRemoteDebuggingPort; #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) CommandLine* command_line = CommandLine::ForCurrentProcess(); @@ -100,6 +117,19 @@ } return webdriver_port; } + +std::string GetWebDriverListenIp() { + // The default port on which the webdriver server should listen for incoming + // connections. + std::string webdriver_listen_ip = + webdriver::WebDriverModule::kDefaultListenIp; + CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kWebDriverListenIp)) { + webdriver_listen_ip = + command_line->GetSwitchValueASCII(switches::kWebDriverListenIp); + } + return webdriver_listen_ip; +} #endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES #endif // ENABLE_WEBDRIVER @@ -164,6 +194,31 @@ } #endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES +std::string GetMinLogLevelString() { +#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) + CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kMinLogLevel)) { + return command_line->GetSwitchValueASCII(switches::kMinLogLevel); + } +#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES + return "info"; +} + +int StringToLogLevel(const std::string& log_level) { + if (log_level == "info") { + return logging::LOG_INFO; + } else if (log_level == "warning") { + return logging::LOG_WARNING; + } else if (log_level == "error") { + return logging::LOG_ERROR; + } else if (log_level == "fatal") { + return logging::LOG_FATAL; + } else { + NOTREACHED() << "Unrecognized logging level: " << log_level; + return logging::LOG_INFO; + } +} + void SetIntegerIfSwitchIsSet(const char* switch_name, int* output) { if (CommandLine::ForCurrentProcess()->HasSwitch(switch_name)) { int32 out; @@ -187,6 +242,8 @@ &options->scratch_surface_cache_size_in_bytes); SetIntegerIfSwitchIsSet(browser::switches::kSkiaCacheSizeInBytes, &options->skia_cache_size_in_bytes); + SetIntegerIfSwitchIsSet(browser::switches::kSoftwareSurfaceCacheSizeInBytes, + &options->software_surface_cache_size_in_bytes); } void ApplyCommandLineSettingsToWebModuleOptions(WebModule::Options* options) { @@ -258,7 +315,18 @@ : message_loop_(MessageLoop::current()), quit_closure_(quit_closure), start_time_(base::TimeTicks::Now()), - stats_update_timer_(true, true) { + stats_update_timer_(true, true), + lite_stats_update_timer_(true, true) { + // Check to see if a timed_trace has been set, indicating that we should + // begin a timed trace upon startup. + base::TimeDelta trace_duration = GetTimedTraceDuration(); + if (trace_duration != base::TimeDelta()) { + trace_event::TraceToFileForDuration( + FilePath(FILE_PATH_LITERAL("timed_trace.json")), trace_duration); + } + + TRACE_EVENT0("cobalt::browser", "Application::Application()"); + DCHECK(MessageLoop::current()); DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); @@ -267,17 +335,16 @@ RegisterUserLogs(); + // Set the minimum logging level, if specified on the command line. + logging::SetMinLogLevel(StringToLogLevel(GetMinLogLevelString())); + stats_update_timer_.Start( FROM_HERE, base::TimeDelta::FromMilliseconds(kStatUpdatePeriodMs), base::Bind(&Application::UpdatePeriodicStats, base::Unretained(this))); - - // Check to see if a timed_trace has been set, indicating that we should - // begin a timed trace upon startup. - base::TimeDelta trace_duration = GetTimedTraceDuration(); - if (trace_duration != base::TimeDelta()) { - trace_event::TraceToFileForDuration( - FilePath(FILE_PATH_LITERAL("timed_trace.json")), trace_duration); - } + lite_stats_update_timer_.Start( + FROM_HERE, base::TimeDelta::FromMilliseconds(kLiteStatUpdatePeriodMs), + base::Bind(&Application::UpdatePeriodicLiteStats, + base::Unretained(this))); // Get the initial URL. GURL initial_url = GetInitialURL(); @@ -374,6 +441,20 @@ DLOG(INFO) << "Use ShellRawVideoDecoderStub"; options.media_module_options.use_video_decoder_stub = true; } + if (command_line->HasSwitch(switches::kMemoryTracker)) { +#if defined(OS_STARBOARD) + using nb::analytics::MemoryTrackerPrintThread; + using nb::analytics::MemoryTracker; + + DLOG(INFO) << "Using MemoryTracking"; + MemoryTracker* memory_tracker = MemoryTracker::Get(); + memory_tracker->InstallGlobalTrackingHooks(); + memory_tracker_print_thread_ = CreateDebugPrintThread(memory_tracker); +#else + DLOG(INFO) + << "Memory tracker is not enabled on non-starboard builds."; +#endif + } #endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES base::optional<math::Size> viewport_size; @@ -430,14 +511,18 @@ base::Bind(&Application::OnApplicationEvent, base::Unretained(this)); event_dispatcher_.AddEventCallback(system_window::ApplicationEvent::TypeId(), application_event_callback_); + deep_link_event_callback_ = + base::Bind(&Application::OnDeepLinkEvent, base::Unretained(this)); + event_dispatcher_.AddEventCallback(base::DeepLinkEvent::TypeId(), + deep_link_event_callback_); #if defined(ENABLE_WEBDRIVER) #if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) if (command_line->HasSwitch(switches::kEnableWebDriver)) { - int webdriver_port = GetWebDriverPort(); web_driver_module_.reset(new webdriver::WebDriverModule( - webdriver_port, base::Bind(&BrowserModule::CreateSessionDriver, - base::Unretained(browser_module_.get())), + GetWebDriverPort(), GetWebDriverListenIp(), + base::Bind(&BrowserModule::CreateSessionDriver, + base::Unretained(browser_module_.get())), base::Bind(&BrowserModule::RequestScreenshotToBuffer, base::Unretained(browser_module_.get())), base::Bind(&BrowserModule::SetProxy, @@ -472,6 +557,13 @@ } Application::~Application() { +#if defined(OS_STARBOARD) + // explicitly reset here because the destruction of the object is complex + // and involves a thread join. If this were to hang the app then having + // the destruction at this point gives a real file-line number and a place + // for the debugger to land. + memory_tracker_print_thread_.reset(NULL); +#endif // Unregister event callbacks. event_dispatcher_.RemoveEventCallback(account::AccountEvent::TypeId(), account_event_callback_); @@ -479,6 +571,8 @@ network_event_callback_); event_dispatcher_.RemoveEventCallback( system_window::ApplicationEvent::TypeId(), application_event_callback_); + event_dispatcher_.RemoveEventCallback( + base::DeepLinkEvent::TypeId(), deep_link_event_callback_); app_status_ = kShutDownAppStatus; } @@ -499,6 +593,7 @@ } void Application::OnAccountEvent(const base::Event* event) { + TRACE_EVENT0("cobalt::browser", "Application::OnAccountEvent()"); const account::AccountEvent* account_event = base::polymorphic_downcast<const account::AccountEvent*>(event); if (account_event->type() == account::AccountEvent::kSignedIn) { @@ -513,6 +608,7 @@ } void Application::OnNetworkEvent(const base::Event* event) { + TRACE_EVENT0("cobalt::browser", "Application::OnNetworkEvent()"); DCHECK(network_event_thread_checker_.CalledOnValidThread()); const network::NetworkEvent* network_event = base::polymorphic_downcast<const network::NetworkEvent*>(event); @@ -533,6 +629,7 @@ } void Application::OnApplicationEvent(const base::Event* event) { + TRACE_EVENT0("cobalt::browser", "Application::OnApplicationEvent()"); DCHECK(application_event_thread_checker_.CalledOnValidThread()); const system_window::ApplicationEvent* app_event = base::polymorphic_downcast<const system_window::ApplicationEvent*>(event); @@ -563,7 +660,18 @@ } } +void Application::OnDeepLinkEvent(const base::Event* event) { + TRACE_EVENT0("cobalt::browser", "Application::OnDeepLinkEvent()"); + const base::DeepLinkEvent* deep_link_event = + base::polymorphic_downcast<const base::DeepLinkEvent*>(event); + // TODO: Remove this when terminal application states are properly handled. + if (deep_link_event->IsH5vccLink()) { + browser_module_->Navigate(GURL(deep_link_event->link())); + } +} + void Application::WebModuleRecreated() { + TRACE_EVENT0("cobalt::browser", "Application::WebModuleRecreated()"); #if defined(ENABLE_WEBDRIVER) if (web_driver_module_) { web_driver_module_->OnWindowRecreated(); @@ -640,7 +748,12 @@ } } +void Application::UpdatePeriodicLiteStats() { + c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_; +} + void Application::UpdatePeriodicStats() { + TRACE_EVENT0("cobalt::browser", "Application::UpdatePeriodicStats()"); #if defined(__LB_SHELL__) bool memory_stats_updated = false; #if !defined(__LB_SHELL__FOR_RELEASE__) @@ -679,8 +792,6 @@ *c_val_stats_.used_gpu_memory = used_gpu_memory; } #endif - - c_val_stats_.app_lifetime = base::TimeTicks::Now() - start_time_; } } // namespace browser
diff --git a/src/cobalt/browser/application.h b/src/cobalt/browser/application.h index 5abba84..a118a1c 100644 --- a/src/cobalt/browser/application.h +++ b/src/cobalt/browser/application.h
@@ -33,6 +33,11 @@ #include "cobalt/debug/debug_web_server.h" #endif +#if defined(OS_STARBOARD) +#include "nb/analytics/memory_tracker.h" +#include "nb/scoped_ptr.h" +#endif + namespace cobalt { namespace browser { @@ -67,6 +72,9 @@ // Called to handle an application event. void OnApplicationEvent(const base::Event* event); + // Called to handle a deep link event. + void OnDeepLinkEvent(const base::Event* event); + // Called when a navigation occurs in the BrowserModule. void WebModuleRecreated(); @@ -88,6 +96,7 @@ base::EventCallback account_event_callback_; base::EventCallback network_event_callback_; base::EventCallback application_event_callback_; + base::EventCallback deep_link_event_callback_; // Thread checkers to ensure that callbacks for network and application events // always occur on the same thread. @@ -150,6 +159,7 @@ void UpdateAndMaybeRegisterUserAgent(); void UpdatePeriodicStats(); + void UpdatePeriodicLiteStats(); static ssize_t available_memory_; static int64 lifetime_in_ms_; @@ -167,6 +177,14 @@ CValStats c_val_stats_; base::Timer stats_update_timer_; + base::Timer lite_stats_update_timer_; + +#if defined(OS_STARBOARD) + // This thread (when active) will print out memory statistics of the engine. + // It is activated by the command line -memory_tracker. + nb::scoped_ptr<nb::analytics::MemoryTrackerPrintThread> + memory_tracker_print_thread_; +#endif }; // Factory method for creating an application. It should be implemented
diff --git a/src/cobalt/browser/browser.gyp b/src/cobalt/browser/browser.gyp index 1115bbd..3572dbb 100644 --- a/src/cobalt/browser/browser.gyp +++ b/src/cobalt/browser/browser.gyp
@@ -75,6 +75,7 @@ '<(DEPTH)/cobalt/webdriver/webdriver.gyp:webdriver', '<(DEPTH)/cobalt/xhr/xhr.gyp:xhr', '<(DEPTH)/googleurl/googleurl.gyp:googleurl', + '<(DEPTH)/nb/nb.gyp:nb', 'browser_bindings.gyp:bindings', 'screen_shot_writer', ], @@ -82,11 +83,6 @@ ['enable_about_scheme == 1', { 'defines': [ 'ENABLE_ABOUT_SCHEME' ], }], - ['OS=="starboard" or (OS=="lb_shell" and target_arch == "ps3")', { - 'dependencies': [ - '<(DEPTH)/nb/nb.gyp:nb', - ], - }], ], },
diff --git a/src/cobalt/browser/browser_bindings.gyp b/src/cobalt/browser/browser_bindings.gyp index 329d8e2..e73cd8b 100644 --- a/src/cobalt/browser/browser_bindings.gyp +++ b/src/cobalt/browser/browser_bindings.gyp
@@ -179,6 +179,8 @@ '../web_animations/KeyframeEffectReadOnly.idl', '../webdriver/ScriptExecutor.idl', + '../webdriver/ScriptExecutorParams.idl', + '../webdriver/ScriptExecutorResult.idl', '../xhr/XMLHttpRequest.idl', '../xhr/XMLHttpRequestEventTarget.idl',
diff --git a/src/cobalt/browser/browser_module.cc b/src/cobalt/browser/browser_module.cc index a6df61e..9a730cc 100644 --- a/src/cobalt/browser/browser_module.cc +++ b/src/cobalt/browser/browser_module.cc
@@ -16,12 +16,16 @@ #include "cobalt/browser/browser_module.h" +#include <vector> + #include "base/bind.h" #include "base/command_line.h" #include "base/debug/trace_event.h" #include "base/logging.h" #include "base/path_service.h" #include "base/stl_util.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" #include "cobalt/base/cobalt_paths.h" #include "cobalt/base/source_location.h" #include "cobalt/base/tokens.h" @@ -33,6 +37,7 @@ #include "cobalt/dom/keycode.h" #include "cobalt/h5vcc/h5vcc.h" #include "cobalt/input/input_device_manager_fuzzer.h" +#include "nb/memory_scope.h" namespace cobalt { namespace browser { @@ -54,6 +59,15 @@ "activated or not. While activated, input will constantly and randomly be " "generated and passed directly into the main web module."; +const char kSetMediaConfigCommand[] = "set_media_config"; +const char kSetMediaConfigCommandShortHelp[] = + "Sets media module configuration."; +const char kSetMediaConfigCommandLongHelp[] = + "This can be called in the form of set_media_config('name=value'), where " + "name is a string and value is an int. Refer to the implementation of " + "MediaModule::SetConfiguration() on individual platform for settings " + "supported on the particular platform."; + #if defined(ENABLE_SCREENSHOT) // Command to take a screenshot. const char kScreenshotCommand[] = "screenshot"; @@ -122,10 +136,6 @@ renderer_module_.pipeline()->GetResourceProvider())), array_buffer_cache_(new dom::ArrayBuffer::Cache(3 * 1024 * 1024)), #endif // defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR) - media_module_(media::MediaModule::Create( - system_window, renderer_module_.render_target()->GetSize(), - renderer_module_.pipeline()->GetResourceProvider(), - options.media_module_options)), network_module_(&storage_manager_, system_window->event_dispatcher(), options.network_module_options), render_tree_combiner_(&renderer_module_, @@ -141,6 +151,10 @@ kFuzzerToggleCommand, base::Bind(&BrowserModule::OnFuzzerToggle, base::Unretained(this)), kFuzzerToggleCommandShortHelp, kFuzzerToggleCommandLongHelp)), + ALLOW_THIS_IN_INITIALIZER_LIST(set_media_config_command_handler_( + kSetMediaConfigCommand, + base::Bind(&BrowserModule::OnSetMediaConfig, base::Unretained(this)), + kSetMediaConfigCommandShortHelp, kSetMediaConfigCommandLongHelp)), #if defined(ENABLE_SCREENSHOT) ALLOW_THIS_IN_INITIALIZER_LIST(screenshot_command_handler_( kScreenshotCommand, @@ -154,6 +168,25 @@ has_resumed_(true, false), will_quit_(false), suspended_(false) { + TRACE_EVENT0("cobalt::browser", "BrowserModule::BrowserModule()"); + // All allocations for media will be tracked by "Media" memory scope. + { + TRACK_MEMORY_SCOPE("Media"); + math::Size output_size = renderer_module_.render_target()->GetSize(); + if (system_window->GetVideoPixelRatio() != 1.f) { + output_size.set_width( + static_cast<int>(static_cast<float>(output_size.width()) * + system_window->GetVideoPixelRatio())); + output_size.set_height( + static_cast<int>(static_cast<float>(output_size.height()) * + system_window->GetVideoPixelRatio())); + } + media_module_ = (media::MediaModule::Create( + system_window, output_size, + renderer_module_.pipeline()->GetResourceProvider(), + options.media_module_options)); + } + // Setup our main web module to have the H5VCC API injected into it. DCHECK(!ContainsKey(web_module_options_.injected_window_attributes, "h5vcc")); h5vcc::H5vcc::Settings h5vcc_settings; @@ -202,6 +235,7 @@ } void BrowserModule::Navigate(const GURL& url) { + TRACE_EVENT0("cobalt::browser", "BrowserModule::Navigate()"); web_module_loaded_.Reset(); // Always post this as a task in case this is being called from the WebModule. @@ -210,6 +244,7 @@ } void BrowserModule::Reload() { + TRACE_EVENT0("cobalt::browser", "BrowserModule::Reload()"); DCHECK_EQ(MessageLoop::current(), self_message_loop_); DCHECK(web_module_); web_module_->ExecuteJavascript( @@ -218,6 +253,7 @@ } void BrowserModule::NavigateInternal(const GURL& url) { + TRACE_EVENT0("cobalt::browser", "BrowserModule::NavigateInternal()"); DCHECK_EQ(MessageLoop::current(), self_message_loop_); // First try the registered handlers (e.g. for h5vcc://). If one of these @@ -241,6 +277,15 @@ renderer_module_.pipeline()->GetResourceProvider(), kLayoutMaxRefreshFrequencyInHz)); +#if defined(OS_STARBOARD) +#if SB_HAS(1_CORE) + // Wait until the splash screen is ready before loading the main web module. + // This prevents starvation of the splash screen module and decoding of the + // splash screen image(s). + splash_screen_->WaitUntilReady(); +#endif +#endif + // Create new WebModule. #if !defined(COBALT_FORCE_CSP) web_module_options_.csp_insecure_allowed_token = @@ -256,6 +301,14 @@ array_buffer_allocator_.get(); options.dom_settings_options.array_buffer_cache = array_buffer_cache_.get(); #endif // defined(ENABLE_GPU_ARRAY_BUFFER_ALLOCATOR) +#if defined(ENABLE_FAKE_MICROPHONE) + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kFakeMicrophone) || + CommandLine::ForCurrentProcess()->HasSwitch(switches::kInputFuzzer)) { + options.dom_settings_options.microphone_options.enable_fake_microphone = + true; + } +#endif // defined(ENABLE_FAKE_MICROPHONE) + options.image_cache_capacity_multiplier_when_playing_video = COBALT_IMAGE_CACHE_CAPACITY_MULTIPLIER_WHEN_PLAYING_VIDEO; web_module_.reset(new WebModule( @@ -272,6 +325,7 @@ } void BrowserModule::OnLoad() { + TRACE_EVENT0("cobalt::browser", "BrowserModule::OnLoad()"); // Repost to our own message loop if necessary. This also prevents // asynchonrous access to this object by |web_module_| during destruction. if (MessageLoop::current() != self_message_loop_) { @@ -284,11 +338,10 @@ web_module_loaded_.Signal(); } -#if defined(ENABLE_WEBDRIVER) bool BrowserModule::WaitForLoad(const base::TimeDelta& timeout) { + TRACE_EVENT0("cobalt::browser", "BrowserModule::WaitForLoad()"); return web_module_loaded_.TimedWait(timeout); } -#endif #if defined(ENABLE_SCREENSHOT) void BrowserModule::RequestScreenshotToFile(const FilePath& path, @@ -374,6 +427,30 @@ } } +void BrowserModule::OnSetMediaConfig(const std::string& config) { + if (MessageLoop::current() != self_message_loop_) { + self_message_loop_->PostTask( + FROM_HERE, + base::Bind(&BrowserModule::OnSetMediaConfig, weak_this_, config)); + return; + } + + std::vector<std::string> tokens; + base::SplitString(config, '=', &tokens); + + int value; + if (tokens.size() != 2 || !base::StringToInt(tokens[1], &value)) { + LOG(WARNING) << "Media configuration '" << config << "' is not in the" + << " form of '<string name>=<int value>'."; + return; + } + if (media_module_->SetConfiguration(tokens[0], value)) { + LOG(INFO) << "Successfully setting " << tokens[0] << " to " << value; + } else { + LOG(WARNING) << "Failed to set " << tokens[0] << " to " << value; + } +} + void BrowserModule::QueueOnDebugConsoleRenderTreeProduced( const browser::WebModule::LayoutResults& layout_results) { TRACE_EVENT0("cobalt::browser", @@ -392,6 +469,11 @@ "BrowserModule::OnDebugConsoleRenderTreeProduced()"); DCHECK_EQ(MessageLoop::current(), self_message_loop_); + if (debug_console_->GetMode() == debug::DebugHub::kDebugConsoleOff) { + render_tree_combiner_.UpdateDebugConsoleRenderTree(base::nullopt); + return; + } + render_tree_combiner_.UpdateDebugConsoleRenderTree(renderer::Submission( layout_results.render_tree, layout_results.layout_time)); } @@ -435,6 +517,7 @@ } void BrowserModule::OnError(const GURL& url, const std::string& error) { + TRACE_EVENT0("cobalt::browser", "BrowserModule::OnError()"); LOG(ERROR) << error; std::string url_string = "h5vcc://network-failure"; @@ -445,6 +528,7 @@ } bool BrowserModule::FilterKeyEvent(const dom::KeyboardEvent::Data& event) { + TRACE_EVENT0("cobalt::browser", "BrowserModule::FilterKeyEvent()"); // Check for hotkeys first. If it is a hotkey, no more processing is needed. if (!FilterKeyEventForHotkeys(event)) { return false; @@ -510,7 +594,10 @@ return false; } -void BrowserModule::DestroySplashScreen() { splash_screen_.reset(NULL); } +void BrowserModule::DestroySplashScreen() { + TRACE_EVENT0("cobalt::browser", "BrowserModule::DestroySplashScreen()"); + splash_screen_.reset(NULL); +} #if defined(ENABLE_WEBDRIVER) scoped_ptr<webdriver::SessionDriver> BrowserModule::CreateSessionDriver( @@ -582,10 +669,12 @@ } void BrowserModule::Suspend() { + TRACE_EVENT0("cobalt::browser", "BrowserModule::Suspend()"); DCHECK_EQ(MessageLoop::current(), self_message_loop_); DCHECK(!suspended_); -// First release the resource provider used by all of our web modules. +// First suspend all our web modules which implies that they will release their +// resource provider and all resources created through it. #if defined(ENABLE_DEBUG_CONSOLE) if (debug_console_) { debug_console_->Suspend(); @@ -623,6 +712,7 @@ } void BrowserModule::Resume() { + TRACE_EVENT0("cobalt::browser", "BrowserModule::Resume()"); DCHECK_EQ(MessageLoop::current(), self_message_loop_); DCHECK(suspended_); @@ -653,6 +743,8 @@ #if defined(OS_STARBOARD) void BrowserModule::OnRendererSubmissionRasterized() { + TRACE_EVENT0("cobalt::browser", + "BrowserModule::OnRendererSubmissionRasterized()"); if (!is_rendered_) { // Hide the system splash screen when the first render has completed. is_rendered_ = true;
diff --git a/src/cobalt/browser/browser_module.h b/src/cobalt/browser/browser_module.h index 669738a..82322b3 100644 --- a/src/cobalt/browser/browser_module.h +++ b/src/cobalt/browser/browser_module.h
@@ -17,8 +17,8 @@ #ifndef COBALT_BROWSER_BROWSER_MODULE_H_ #define COBALT_BROWSER_BROWSER_MODULE_H_ -#include <list> #include <string> +#include <vector> #include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" @@ -71,7 +71,7 @@ // Type for a collection of URL handler callbacks that can potentially handle // a URL before using it to initialize a new WebModule. - typedef std::list<URLHandler::URLHandlerCallback> URLHandlerCollection; + typedef std::vector<URLHandler::URLHandlerCallback> URLHandlerCollection; BrowserModule(const GURL& url, system_window::SystemWindow* system_window, account::AccountManager* account_manager, @@ -127,12 +127,10 @@ // Called when the WebModule's Window.onload event is fired. void OnLoad(); -#if defined(ENABLE_WEBDRIVER) // Wait for the onload event to be fired with the specified timeout. If the // webmodule is not currently loading the document, this will return // immediately. bool WaitForLoad(const base::TimeDelta& timeout); -#endif // Glue function to deal with the production of the main render tree, // and will manage handing it off to the renderer. @@ -182,6 +180,10 @@ // Toggles the input fuzzer on/off. Ignores the parameter. void OnFuzzerToggle(const std::string&); + // Use the config in the form of '<string name>=<int value>' to call + // MediaModule::SetConfiguration(). + void OnSetMediaConfig(const std::string& config); + // Glue function to deal with the production of the debug console render tree, // and will manage handing it off to the renderer. void QueueOnDebugConsoleRenderTreeProduced( @@ -299,9 +301,12 @@ TraceManager trace_manager; - // Command handler object for toggline the input fuzzer on/off. + // Command handler object for toggling the input fuzzer on/off. base::ConsoleCommandManager::CommandHandler fuzzer_toggle_command_handler_; + // Command handler object for setting media module config. + base::ConsoleCommandManager::CommandHandler set_media_config_command_handler_; + #if defined(ENABLE_SCREENSHOT) // Command handler object for screenshot command from the debug console. base::ConsoleCommandManager::CommandHandler screenshot_command_handler_;
diff --git a/src/cobalt/browser/cobalt.gyp b/src/cobalt/browser/cobalt.gyp index 4f73099..f5bcfe9 100644 --- a/src/cobalt/browser/cobalt.gyp +++ b/src/cobalt/browser/cobalt.gyp
@@ -94,4 +94,17 @@ }, ], + 'conditions': [ + ['final_executable_type == "shared_library"', { + 'targets': [ + { + 'target_name': 'cobalt_bin', + 'type': 'executable', + 'dependencies': [ + 'cobalt', + ], + }, + ], + }], + ], }
diff --git a/src/cobalt/browser/debug_console/debug_console.css b/src/cobalt/browser/debug_console/debug_console.css index afac884..3df005f 100644 --- a/src/cobalt/browser/debug_console/debug_console.css +++ b/src/cobalt/browser/debug_console/debug_console.css
@@ -10,6 +10,7 @@ right: 0; background-color: rgba(128, 128, 128, 0.6); color: #FFFFFF; + display: none; } #hud { @@ -35,6 +36,7 @@ background-color: rgba(128, 128, 128, 0.6); color: #FFFFFF; overflow: hidden; + display: none; } #messageContainerFrame {
diff --git a/src/cobalt/browser/debug_console/debug_console.js b/src/cobalt/browser/debug_console/debug_console.js index 58282d5..7c2f8a5 100644 --- a/src/cobalt/browser/debug_console/debug_console.js +++ b/src/cobalt/browser/debug_console/debug_console.js
@@ -258,6 +258,7 @@ createCommandInput(); createMessageLog(); createDebuggerClient(); + showHud(false); showConsole(false); createConsoleValues(); initDebugCommands();
diff --git a/src/cobalt/browser/render_tree_combiner.cc b/src/cobalt/browser/render_tree_combiner.cc index 364158b..6a076e1 100644 --- a/src/cobalt/browser/render_tree_combiner.cc +++ b/src/cobalt/browser/render_tree_combiner.cc
@@ -45,7 +45,7 @@ } void RenderTreeCombiner::UpdateDebugConsoleRenderTree( - const renderer::Submission& render_tree_submission) { + const base::optional<renderer::Submission>& render_tree_submission) { debug_console_render_tree_ = render_tree_submission; SubmitToRenderer(); } @@ -105,7 +105,7 @@ } void RenderTreeCombiner::UpdateDebugConsoleRenderTree( - const renderer::Submission& render_tree_submission) { + const base::optional<renderer::Submission>& render_tree_submission) { UNREFERENCED_PARAMETER(render_tree_submission); } #endif // ENABLE_DEBUG_CONSOLE
diff --git a/src/cobalt/browser/render_tree_combiner.h b/src/cobalt/browser/render_tree_combiner.h index 44f38d6..75bca9b 100644 --- a/src/cobalt/browser/render_tree_combiner.h +++ b/src/cobalt/browser/render_tree_combiner.h
@@ -40,7 +40,7 @@ // Update the debug console render tree. void UpdateDebugConsoleRenderTree( - const renderer::Submission& render_tree_submission); + const base::optional<renderer::Submission>& render_tree_submission); #if defined(ENABLE_DEBUG_CONSOLE) bool render_debug_console() const { return render_debug_console_; }
diff --git a/src/cobalt/browser/splash_screen.cc b/src/cobalt/browser/splash_screen.cc index a2a3d97..0ccc88c 100644 --- a/src/cobalt/browser/splash_screen.cc +++ b/src/cobalt/browser/splash_screen.cc
@@ -30,14 +30,17 @@ render_tree_produced_callback, network::NetworkModule* network_module, const math::Size& window_dimensions, render_tree::ResourceProvider* resource_provider, float layout_refresh_rate, - const SplashScreen::Options& options) { + const SplashScreen::Options& options) + : render_tree_produced_callback_(render_tree_produced_callback) + , is_ready_(true, false) { WebModule::Options web_module_options; web_module_options.name = "SplashScreenWebModule"; web_module_.reset(new WebModule( - options.url, render_tree_produced_callback, + options.url, + base::Bind(&SplashScreen::OnRenderTreeProduced, base::Unretained(this)), base::Bind(&SplashScreen::OnError, base::Unretained(this)), - base::Closure(), /* window_close_callback */ + base::Bind(&SplashScreen::OnWindowClosed, base::Unretained(this)), &stub_media_module_, network_module, window_dimensions, resource_provider, layout_refresh_rate, web_module_options)); } @@ -47,5 +50,19 @@ web_module_->Resume(resource_provider); } +void SplashScreen::WaitUntilReady() { + is_ready_.Wait(); +} + +void SplashScreen::OnRenderTreeProduced( + const browser::WebModule::LayoutResults& layout_results) { + is_ready_.Signal(); + render_tree_produced_callback_.Run(layout_results); +} + +void SplashScreen::OnWindowClosed() { + is_ready_.Signal(); +} + } // namespace browser } // namespace cobalt
diff --git a/src/cobalt/browser/splash_screen.h b/src/cobalt/browser/splash_screen.h index a105fe4..7888fa8 100644 --- a/src/cobalt/browser/splash_screen.h +++ b/src/cobalt/browser/splash_screen.h
@@ -21,6 +21,7 @@ #include "base/logging.h" #include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" #include "cobalt/browser/web_module.h" #include "cobalt/media/media_module_stub.h" #include "googleurl/src/gurl.h" @@ -48,13 +49,28 @@ void Suspend(); void Resume(render_tree::ResourceProvider* resource_provider); + // Block the caller until the splash screen is ready to be rendered. + void WaitUntilReady(); + private: + void OnRenderTreeProduced( + const browser::WebModule::LayoutResults& layout_results); + void OnError(const GURL& /* url */, const std::string& error) { + is_ready_.Signal(); LOG(ERROR) << error; } + void OnWindowClosed(); + media::MediaModuleStub stub_media_module_; scoped_ptr<WebModule> web_module_; + + WebModule::OnRenderTreeProducedCallback render_tree_produced_callback_; + + // Signalled once the splash screen has produced its first render tree or + // an error occurred. + base::WaitableEvent is_ready_; }; } // namespace browser
diff --git a/src/cobalt/browser/stack_size_constants.h b/src/cobalt/browser/stack_size_constants.h new file mode 100644 index 0000000..c4e62ea --- /dev/null +++ b/src/cobalt/browser/stack_size_constants.h
@@ -0,0 +1,38 @@ +/* + * Copyright 2016 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_BROWSER_STACK_SIZE_CONSTANTS_H_ +#define COBALT_BROWSER_STACK_SIZE_CONSTANTS_H_ + +#include "cobalt/base/address_sanitizer.h" + +namespace cobalt { +namespace browser { +#if defined(COBALT_BUILD_TYPE_DEBUG) + // Non-optimized builds require a bigger stack size. + const size_t kBaseStackSize = 2 * 1024 * 1024; +#elif defined(COBALT_BUILD_TYPE_DEVEL) + // Devel builds require a slightly bigger stack size. + const size_t kBaseStackSize = 448 * 1024; +#else + const size_t kBaseStackSize = 384 * 1024; +#endif + const size_t kWebModuleStackSize = + kBaseStackSize + base::kAsanAdditionalStackSize; +} // namespace browser +} // namespace cobalt + +#endif // COBALT_BROWSER_STACK_SIZE_CONSTANTS_H_
diff --git a/src/cobalt/browser/switches.cc b/src/cobalt/browser/switches.cc index a402253..91e0e6d 100644 --- a/src/cobalt/browser/switches.cc +++ b/src/cobalt/browser/switches.cc
@@ -45,6 +45,10 @@ // Additional base directory for accessing web files via file://. const char kExtraWebFileDir[] = "web_file_path"; +// If this flag is set, fake microphone will be used to mock the user voice +// input. +const char kFakeMicrophone[] = "fake_microphone"; + // Setting this switch causes all certificate errors to be ignored. const char kIgnoreCertificateErrors[] = "ignore_certificate_errors"; @@ -52,6 +56,9 @@ // taken from an external input device (like a controller). const char kInputFuzzer[] = "input_fuzzer"; +// Set the minimum logging level: info|warning|error|fatal. +const char kMinLogLevel[] = "min_log_level"; + // Use the NullAudioStreamer. Audio will be decoded but will not play back. No // audio output library will be initialized or used. const char kNullAudioStreamer[] = "null_audio_streamer"; @@ -94,6 +101,13 @@ // Port that the WebDriver server should be listening on. const char kWebDriverPort[] = "webdriver_port"; +// IP that the WebDriver server should be listening on. +// (INADDR_ANY if unspecified). +const char kWebDriverListenIp[] = "webdriver_listen_ip"; + +// Enables memory tracking by installing the memory tracker on startup. +const char kMemoryTracker[] = "memory_tracker"; + #endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES // Determines the capacity of the image cache which manages image surfaces @@ -126,6 +140,13 @@ // setting may affect GPU memory usage. const char kSkiaCacheSizeInBytes[] = "skia_cache_size_in_bytes"; +// Only relevant if you are using the Blitter API. +// Determines the capacity of the software surface cache, which is used to +// cache all surfaces that are rendered via a software rasterizer to avoid +// re-rendering them. +const char kSoftwareSurfaceCacheSizeInBytes[] = + "software_surface_cache_size_in_bytes"; + // Determines the capacity of the surface cache. The surface cache tracks which // render tree nodes are being re-used across frames and stores the nodes that // are most CPU-expensive to render into surfaces. While it depends on the
diff --git a/src/cobalt/browser/switches.h b/src/cobalt/browser/switches.h index 7d463d0..7ef6a28 100644 --- a/src/cobalt/browser/switches.h +++ b/src/cobalt/browser/switches.h
@@ -29,8 +29,10 @@ extern const char kDisableWebmVp9[]; extern const char kEnableWebDriver[]; extern const char kExtraWebFileDir[]; +extern const char kFakeMicrophone[]; extern const char kIgnoreCertificateErrors[]; extern const char kInputFuzzer[]; +extern const char kMinLogLevel[]; extern const char kNullAudioStreamer[]; extern const char kNullSavegame[]; extern const char kPartialLayout[]; @@ -42,6 +44,8 @@ extern const char kVideoContainerSizeOverride[]; extern const char kVideoDecoderStub[]; extern const char kWebDriverPort[]; +extern const char kWebDriverListenIp[]; +extern const char kMemoryTracker[]; #endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES extern const char kImageCacheSizeInBytes[]; @@ -49,6 +53,7 @@ extern const char kRemoteTypefaceCacheSizeInBytes[]; extern const char kScratchSurfaceCacheSizeInBytes[]; extern const char kSkiaCacheSizeInBytes[]; +extern const char kSoftwareSurfaceCacheSizeInBytes[]; extern const char kSurfaceCacheSizeInBytes[]; extern const char kViewport[];
diff --git a/src/cobalt/browser/testdata/mtm-demo/README.txt b/src/cobalt/browser/testdata/mtm-demo/README.txt new file mode 100644 index 0000000..5368357 --- /dev/null +++ b/src/cobalt/browser/testdata/mtm-demo/README.txt
@@ -0,0 +1,37 @@ +These test whether the presence of the CSS MTM filter affect the path taken in +rendering the video replaced box. <normal.html> has a video tag without the +filter, while <mtm.html> has a video with the filter applied to it. + +To test locally, all 3 of mtm.html, normal.html and progressive.mp4 have to be +hosted on a server that supports HTTP RANGE header, which is needed for video to +be loaded correctly. One such server is the extension to the Python +SimpleHTTPServer at https://github.com/danvk/RangeHTTPServer (run from this +directory, "cobalt/src/cobalt/browser/testdata/mtm-demo/"): + + $ sudo apt-get install python-pip + $ pip install --user rangehttpserver + $ python -m RangeHTTPServer + +Test video without the MTM filter (should render normally, run from cobalt/src +directory): + + $ out/linux-x64x11_debug/cobalt --csp_mode=disable --allow_http --url=http://localhost:8000/normal.html + +Test the video with the MTM filter (should NOT render in its bounding box in the +document, and in the presence of the correct rasterizer, should be rendered onto +its own texture off the main UI layout): + + $ out/linux-x64x11_debug/cobalt --csp_mode=disable --allow_http --url=http://localhost:8000/mtm.html + +There is also the option of using the google cloud utils and the cloud storage +to publish these files, as is done with the other demos. Care should be taken +that nothing confidential gets uploaded with these files. "public-read" is +needed in the ACL because linking and video srcing does not work without it. + +A bucket specifically for this demo already exists at "gs://yt-cobalt-mtm-test", +but uploading to it often might be problematic since buckets are not verison- +controlled: + + $ gsutil cp -a public-read cobalt/browser/testdata/mtm-demo/normal.html cobalt/browser/testdata/mtm-demo/mtm.html cobalt/browser/testdata/mtm-demo/progressive.mp4 gs://yt-cobalt-mtm-test/ + $ out/linux-x64x11_debug/cobalt --csp_mode=disable --url=https://storage.googleapis.com/yt-cobalt-mtm-test/mtm.html + $ out/linux-x64x11_debug/cobalt --csp_mode=disable --url=https://storage.googleapis.com/yt-cobalt-mtm-test/normal.html
diff --git a/src/cobalt/browser/testdata/mtm-demo/mtm.html b/src/cobalt/browser/testdata/mtm-demo/mtm.html new file mode 100644 index 0000000..6877674 --- /dev/null +++ b/src/cobalt/browser/testdata/mtm-demo/mtm.html
@@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html> +<head> + <title>mtm Demo</title> + <style> + body { + background-color: rgb(255, 255, 255); + color: #0047ab; + font-size: 100px; + } + .vid { + margin: 100px; + border: 10px solid blue; + width: 960px; + height: 540px; + filter: -cobalt-mtm(url(projection.msh), 100deg 60deg, + matrix3d(1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1)); + } + </style> +</head> +<body> + <div>Mtm Demo!!!</div> + <video autoplay loop id="v" class="vid" src="progressive.mp4"></video> +</body> +</html>
diff --git a/src/cobalt/browser/testdata/mtm-demo/normal.html b/src/cobalt/browser/testdata/mtm-demo/normal.html new file mode 100644 index 0000000..3d516f6 --- /dev/null +++ b/src/cobalt/browser/testdata/mtm-demo/normal.html
@@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> +<head> + <title>Normal Demo</title> + <style> + body { + background-color: rgb(255, 255, 255); + color: #0047ab; + font-size: 100px; + } + .vid { + margin: 100px; + border: 10px solid blue; + width: 960px; + height: 540px; + } + </style> +</head> +<body> + <div>Normal Demo</div> + <video autoplay loop id="v" class="vid" src="progressive.mp4"></video> +</body> +</html>
diff --git a/src/cobalt/browser/testdata/mtm-demo/progressive.mp4 b/src/cobalt/browser/testdata/mtm-demo/progressive.mp4 new file mode 100644 index 0000000..2686a9b --- /dev/null +++ b/src/cobalt/browser/testdata/mtm-demo/progressive.mp4 Binary files differ
diff --git a/src/cobalt/browser/web_module.cc b/src/cobalt/browser/web_module.cc index 3c26c18..defb344 100644 --- a/src/cobalt/browser/web_module.cc +++ b/src/cobalt/browser/web_module.cc
@@ -23,16 +23,22 @@ #include "base/message_loop_proxy.h" #include "base/optional.h" #include "base/stringprintf.h" -#include "cobalt/base/address_sanitizer.h" #include "cobalt/base/c_val.h" #include "cobalt/base/poller.h" #include "cobalt/base/tokens.h" +#include "cobalt/browser/stack_size_constants.h" #include "cobalt/browser/switches.h" #include "cobalt/browser/web_module_stat_tracker.h" +#include "cobalt/css_parser/parser.h" #include "cobalt/debug/debug_server_module.h" +#include "cobalt/dom/blob.h" #include "cobalt/dom/csp_delegate_factory.h" +#include "cobalt/dom/local_storage_database.h" #include "cobalt/dom/storage.h" +#include "cobalt/dom/url.h" +#include "cobalt/dom_parser/parser.h" #include "cobalt/h5vcc/h5vcc.h" +#include "cobalt/script/javascript_engine.h" #include "cobalt/storage/storage_manager.h" namespace cobalt { @@ -40,11 +46,11 @@ namespace { -#if defined(COBALT_RELEASE) +#if defined(COBALT_BUILD_TYPE_GOLD) const int kPollerPeriodMs = 2000; -#else // #if defined(COBALT_RELEASE) +#else // #if defined(COBALT_BUILD_TYPE_GOLD) const int kPollerPeriodMs = 20; -#endif // #if defined(COBALT_RELEASE) +#endif // #if defined(COBALT_BUILD_TYPE_GOLD) // The maximum number of element depth in the DOM tree. Elements at a level // deeper than this could be discarded, and will not be rendered. @@ -112,7 +118,8 @@ #endif // ENABLE_DEBUG_CONSOLE // Called to inject a keyboard event into the web module. - void InjectKeyboardEvent(const dom::KeyboardEvent::Data& event); + void InjectKeyboardEvent(scoped_refptr<dom::Element> element, + const dom::KeyboardEvent::Data& event); // Called to execute JavaScript in this WebModule. Sets the |result| // output parameter and signals |got_result|. @@ -140,7 +147,11 @@ void CreateDebugServerIfNull(); #endif // ENABLE_DEBUG_CONSOLE - void Suspend(); + // Suspension of the WebModule is a two-part process since a message loop + // gap is needed in order to give a chance to handle loader callbacks + // that were initiated from a loader thread. + void SuspendLoaders(); + void FinishSuspend(); void Resume(render_tree::ResourceProvider* resource_provider); private: @@ -238,6 +249,9 @@ // Object to register and retrieve MediaSource object with a string key. scoped_ptr<dom::MediaSource::Registry> media_source_registry_; + // Object to register and retrieve Blob objects with a string key. + scoped_ptr<dom::Blob::Registry> blob_registry_; + // The Window object wraps all DOM-related components. scoped_refptr<dom::Window> window_; @@ -310,8 +324,11 @@ base::Bind(&WebModule::Impl::OnError, base::Unretained(this)))); DCHECK(dom_parser_); + blob_registry_.reset(new dom::Blob::Registry); + fetcher_factory_.reset(new loader::FetcherFactory( - data.network_module, data.options.extra_web_file_dir)); + data.network_module, data.options.extra_web_file_dir, + dom::URL::MakeBlobResolverCallback(blob_registry_.get()))); DCHECK(fetcher_factory_); loader_factory_.reset( @@ -389,8 +406,9 @@ environment_settings_.reset(new dom::DOMSettings( kDOMMaxElementDepth, fetcher_factory_.get(), data.network_module, window_, - media_source_registry_.get(), javascript_engine_.get(), - global_environment_.get(), data.options.dom_settings_options)); + media_source_registry_.get(), blob_registry_.get(), data.media_module, + javascript_engine_.get(), global_environment_.get(), + data.options.dom_settings_options)); DCHECK(environment_settings_); global_environment_->CreateGlobalObject(window_, environment_settings_.get()); @@ -457,6 +475,7 @@ window_weak_.reset(); window_ = NULL; media_source_registry_.reset(); + blob_registry_.reset(); script_runner_.reset(); execution_state_.reset(); global_environment_ = NULL; @@ -472,6 +491,7 @@ } void WebModule::Impl::InjectKeyboardEvent( + scoped_refptr<dom::Element> element, const dom::KeyboardEvent::Data& event) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(is_running_); @@ -486,7 +506,11 @@ // injected. web_module_stat_tracker_->OnInjectEvent(keyboard_event); - window_->InjectEvent(keyboard_event); + if (element) { + element->DispatchEvent(keyboard_event); + } else { + window_->InjectEvent(keyboard_event); + } } void WebModule::Impl::ExecuteJavascript( @@ -563,6 +587,7 @@ window_driver_out->reset(new webdriver::WindowDriver( window_id, window_weak_, base::Bind(&WebModule::Impl::global_environment, base::Unretained(this)), + base::Bind(&WebModule::Impl::InjectKeyboardEvent, base::Unretained(this)), base::MessageLoopProxy::current())); } #endif // defined(ENABLE_WEBDRIVER) @@ -597,21 +622,20 @@ } } -void WebModule::Impl::Suspend() { - TRACE_EVENT0("cobalt::browser", "WebModule::Impl::Suspend()"); - DCHECK(resource_provider_); +void WebModule::Impl::SuspendLoaders() { + TRACE_EVENT0("cobalt::browser", "WebModule::Impl::SuspendLoaders()"); // Stop the generation of render trees. layout_manager_->Suspend(); -#if defined(ENABLE_DEBUG_CONSOLE) - // The debug overlay may be holding onto a render tree, clear that out. - debug_overlay_->ClearInput(); -#endif - // Clear out the loader factory's resource provider, possibly aborting any // in-progress loads. loader_factory_->Suspend(); +} + +void WebModule::Impl::FinishSuspend() { + TRACE_EVENT0("cobalt::browser", "WebModule::Impl::FinishSuspend()"); + DCHECK(resource_provider_); // Ensure the document is not holding onto any more image cached resources so // that they are eligible to be purged. @@ -624,6 +648,11 @@ image_cache_->Purge(); remote_typeface_cache_->Purge(); +#if defined(ENABLE_DEBUG_CONSOLE) + // The debug overlay may be holding onto a render tree, clear that out. + debug_overlay_->ClearInput(); +#endif + // Finally mark that we have no resource provider. resource_provider_ = NULL; } @@ -676,18 +705,11 @@ window_close_callback, media_module, network_module, window_dimensions, resource_provider, kDOMMaxElementDepth, layout_refresh_rate, options); -#if defined(COBALT_BUILD_TYPE_DEBUG) - // Non-optimized builds require a bigger stack size. - const size_t kBaseStackSize = 2 * 1024 * 1024; -#else - const size_t kBaseStackSize = 256 * 1024; -#endif - // Start the dedicated thread and create the internal implementation // object on that thread. - size_t stack_size = kBaseStackSize + base::kAsanAdditionalStackSize; thread_.StartWithOptions( - base::Thread::Options(MessageLoop::TYPE_DEFAULT, stack_size)); + base::Thread::Options(MessageLoop::TYPE_DEFAULT, + cobalt::browser::kWebModuleStackSize)); DCHECK(message_loop()); message_loop()->PostTask( @@ -759,14 +781,23 @@ } void WebModule::InjectKeyboardEvent(const dom::KeyboardEvent::Data& event) { + DCHECK(message_loop()); + DCHECK(impl_); + message_loop()->PostTask(FROM_HERE, + base::Bind(&WebModule::Impl::InjectKeyboardEvent, + base::Unretained(impl_.get()), + scoped_refptr<dom::Element>(), event)); +} + +void WebModule::InjectKeyboardEvent(scoped_refptr<dom::Element> element, + const dom::KeyboardEvent::Data& event) { TRACE_EVENT1("cobalt::browser", "WebModule::InjectKeyboardEvent()", "type", event.type); DCHECK(message_loop()); DCHECK(impl_); + DCHECK_EQ(MessageLoop::current(), message_loop()); - message_loop()->PostTask(FROM_HERE, - base::Bind(&WebModule::Impl::InjectKeyboardEvent, - base::Unretained(impl_.get()), event)); + impl_->InjectKeyboardEvent(element, event); } std::string WebModule::ExecuteJavascript( @@ -854,16 +885,43 @@ #endif // defined(ENABLE_DEBUG_CONSOLE) void WebModule::Suspend() { - message_loop()->PostTask( - FROM_HERE, - base::Bind(&WebModule::Impl::Suspend, base::Unretained(impl_.get()))); + TRACE_EVENT0("cobalt::browser", "WebModule::Suspend()"); - base::WaitableEvent resource_provider_released(true, false); - message_loop()->PostTask( - FROM_HERE, base::Bind(&base::WaitableEvent::Signal, - base::Unretained(&resource_provider_released))); + // Suspend() must only be called by a thread external from the WebModule + // thread. + DCHECK_NE(MessageLoop::current(), message_loop()); - resource_provider_released.Wait(); + base::WaitableEvent task_finished(false /* automatic reset */, + false /* initially unsignaled */); + + // Suspension of the WebModule is orchestrated here in two phases. + // 1) Send a signal to suspend WebModule loader activity and cancel any + // in-progress loads. Since loading may occur from any thread, this may + // result in cancel/completion callbacks being posted to message_loop(). + message_loop()->PostTask(FROM_HERE, + base::Bind(&WebModule::Impl::SuspendLoaders, + base::Unretained(impl_.get()))); + + // Wait for the suspension task to complete before proceeding. + message_loop()->PostTask(FROM_HERE, + base::Bind(&base::WaitableEvent::Signal, + base::Unretained(&task_finished))); + task_finished.Wait(); + + // 2) Now append to the task queue a task to complete the suspension process. + // Between 1 and 2, tasks may have been registered to handle resource load + // completion events, and so this FinishSuspend task will be executed after + // the load completions are all resolved. + message_loop()->PostTask(FROM_HERE, + base::Bind(&WebModule::Impl::FinishSuspend, + base::Unretained(impl_.get()))); + + // Wait for suspension to fully complete on the WebModule thread before + // continuing. + message_loop()->PostTask(FROM_HERE, + base::Bind(&base::WaitableEvent::Signal, + base::Unretained(&task_finished))); + task_finished.Wait(); } void WebModule::Resume(render_tree::ResourceProvider* resource_provider) {
diff --git a/src/cobalt/browser/web_module.h b/src/cobalt/browser/web_module.h index 8907911..34f38b2 100644 --- a/src/cobalt/browser/web_module.h +++ b/src/cobalt/browser/web_module.h
@@ -27,6 +27,7 @@ #include "base/synchronization/waitable_event.h" #include "base/threading/thread.h" #include "base/threading/thread_checker.h" +#include "cobalt/base/address_sanitizer.h" #include "cobalt/base/console_commands.h" #include "cobalt/base/source_location.h" #include "cobalt/css_parser/parser.h" @@ -34,6 +35,7 @@ #include "cobalt/debug/debug_server.h" #include "cobalt/debug/render_overlay.h" #endif // ENABLE_DEBUG_CONSOLE +#include "cobalt/dom/blob.h" #include "cobalt/dom/csp_delegate.h" #include "cobalt/dom/dom_settings.h" #include "cobalt/dom/keyboard_event.h" @@ -159,6 +161,13 @@ ~WebModule(); // Call this to inject a keyboard event into the web module. + // Event is directed at a specific element if the element is non-null. + // Otherwise, the currently focused element receives the event. + // If element is specified, we must be on the WebModule's message loop + void InjectKeyboardEvent(scoped_refptr<dom::Element> element, + const dom::KeyboardEvent::Data& event); + + // Call this to inject a keyboard event into the web module. void InjectKeyboardEvent(const dom::KeyboardEvent::Data& event); // Call this to execute Javascript code in this web module. The calling
diff --git a/src/cobalt/browser/web_module_stat_tracker.cc b/src/cobalt/browser/web_module_stat_tracker.cc index 10bdf94..aa4d634 100644 --- a/src/cobalt/browser/web_module_stat_tracker.cc +++ b/src/cobalt/browser/web_module_stat_tracker.cc
@@ -28,7 +28,10 @@ : dom_stat_tracker_(new dom::DomStatTracker(name)), layout_stat_tracker_(new layout::LayoutStatTracker(name)), should_track_event_stats_(should_track_event_stats), - current_event_type_(kEventTypeInvalid) { + current_event_type_(kEventTypeInvalid), + name_(name), + event_is_processing_(StringPrintf("Event.%s.IsProcessing", name.c_str()), + 0, "Nonzero when an event is being processed.") { if (should_track_event_stats_) { event_stats_.reserve(kNumEventTypes); for (int i = 0; i < kNumEventTypes; ++i) { @@ -56,6 +59,8 @@ EndCurrentEvent(false); + event_is_processing_ = 1; + if (event->type() == base::Tokens::keydown()) { current_event_type_ = kEventTypeKeyDown; } else if (event->type() == base::Tokens::keyup()) { @@ -152,6 +157,8 @@ return; } + event_is_processing_ = 0; + stop_watch_durations_[kStopWatchTypeEvent] = base::TimeDelta(); stop_watches_[kStopWatchTypeEvent].Stop(); dom_stat_tracker_->DisableStopWatches();
diff --git a/src/cobalt/browser/web_module_stat_tracker.h b/src/cobalt/browser/web_module_stat_tracker.h index 8e9dbd3..5b317e7 100644 --- a/src/cobalt/browser/web_module_stat_tracker.h +++ b/src/cobalt/browser/web_module_stat_tracker.h
@@ -115,6 +115,10 @@ // Stop watch-related std::vector<base::StopWatch> stop_watches_; std::vector<base::TimeDelta> stop_watch_durations_; + + std::string name_; + + base::CVal<int, base::CValPublic> event_is_processing_; }; } // namespace browser
diff --git a/src/cobalt/build/all.gyp b/src/cobalt/build/all.gyp index 8352629..52a802c 100644 --- a/src/cobalt/build/all.gyp +++ b/src/cobalt/build/all.gyp
@@ -59,11 +59,13 @@ '<(DEPTH)/cobalt/samples/samples.gyp:*', '<(DEPTH)/cobalt/script/script.gyp:*', '<(DEPTH)/cobalt/script/engine.gyp:all_engines', + '<(DEPTH)/cobalt/speech/sandbox/sandbox.gyp:*', '<(DEPTH)/cobalt/speech/speech.gyp:*', '<(DEPTH)/cobalt/storage/storage.gyp:*', '<(DEPTH)/cobalt/trace_event/trace_event.gyp:*', '<(DEPTH)/cobalt/web_animations/web_animations.gyp:*', '<(DEPTH)/cobalt/webdriver/webdriver.gyp:*', + '<(DEPTH)/cobalt/webdriver/webdriver_test.gyp:*', '<(DEPTH)/cobalt/xhr/xhr.gyp:*', '<(DEPTH)/crypto/crypto.gyp:crypto_unittests', '<(DEPTH)/sql/sql.gyp:sql_unittests',
diff --git a/src/cobalt/build/build.id b/src/cobalt/build/build.id index 159bca5..e0a264e 100644 --- a/src/cobalt/build/build.id +++ b/src/cobalt/build/build.id
@@ -1 +1 @@ -15147 \ No newline at end of file +16000 \ No newline at end of file
diff --git a/src/cobalt/build/config/base.gypi b/src/cobalt/build/config/base.gypi index 7170bcb..53cc9b4 100644 --- a/src/cobalt/build/config/base.gypi +++ b/src/cobalt/build/config/base.gypi
@@ -67,6 +67,13 @@ # platform. 'default_renderer_options_dependency%': '<(DEPTH)/cobalt/renderer/default_options_starboard.gyp:default_options', + # 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. + 'cobalt_minimum_frame_time_in_milliseconds%': '0', + # The variables allow changing the target type on platforms where the # native code may require an additional packaging step (ex. Android). 'gtest_target_type%': 'executable', @@ -95,9 +102,11 @@ 'lbshell_root%': '<(DEPTH)/lbshell', # The relative path from src/ to the directory containing the - # starboard_platform.gyp file, or the empty string if not an autodiscovered - # platform. - 'starboard_path%': '', + # starboard_platform.gyp file. It is currently set to + # 'starboard/<(target_arch)' to make semi-starboard platforms work. + # TODO: Set the default value to '' once all semi-starboard platforms are + # moved to starboard. + 'starboard_path%': 'starboard/<(target_arch)', # The source of EGL and GLES headers and libraries. # Valid values (case and everything sensitive!): @@ -128,6 +137,7 @@ # - scratch_surface_cache_size_in_bytes # - surface_cache_size_in_bytes # - image_cache_size_in_bytes + # - skia_glyph_atlas_width * skia_glyph_atlas_height # # The other caches affect CPU memory usage. @@ -159,6 +169,12 @@ # typefaces downloaded from a web page. 'remote_typeface_cache_size_in_bytes%': 5 * 1024 * 1024, + # Only relevant if you are using the Blitter API. + # Determines the capacity of the software surface cache, which is used to + # cache all surfaces that are rendered via a software rasterizer to avoid + # re-rendering them. + 'software_surface_cache_size_in_bytes%': 10 * 1024 * 1024, + # Modifying this value to be non-1.0f will result in the image cache # capacity being cleared and then temporarily reduced for the duration that # a video is playing. This can be useful for some platforms if they are @@ -168,6 +184,15 @@ # image_cache_capacity_multiplier_when_playing_video. 'image_cache_capacity_multiplier_when_playing_video%': '1.0f', + # Determines the size in pixels of the glyph atlas where rendered glyphs are + # cached. The resulting memory usage is 2 bytes of GPU memory per pixel. + # When a value is used that is too small, thrashing may occur that will + # result in visible stutter. Such thrashing is more likely to occur when CJK + # language glyphs are rendered and when the size of the glyphs in pixels is + # larger, such as for higher resolution displays. + 'skia_glyph_atlas_width%': '2048', + 'skia_glyph_atlas_height%': '2048', + # Compiler configuration. # The following variables are used to specify compiler and linker @@ -226,8 +251,10 @@ 'werror': '', # Cobalt doesn't currently support tcmalloc. 'linux_use_tcmalloc': 0, - - 'enable_webdriver%': 0, + # The event polling mechanism available on this platform to support libevent. + # Platforms may redefine to 'poll' if necessary. + # Other mechanisms, e.g. devpoll, kqueue, select, are not yet supported. + 'sb_libevent_method%': 'epoll', }, 'target_defaults': { @@ -268,13 +295,25 @@ 'include_dirs': [ '<(DEPTH)' ], 'libraries': [ '<@(platform_libraries)' ], - # TODO: This is needed to support the option to include - # posix_emulation.h to all compiled source files. This dependency should - # be refactored and removed. - 'include_dirs_target': [ - '<(DEPTH)/lbshell/src', - ], 'conditions': [ + ['final_executable_type=="shared_library"', { + 'target_conditions': [ + ['_toolset=="target"', { + 'defines': [ + # Rewrite main() functions into StarboardMain. TODO: This is a + # hack, it would be better to be more surgical, here. + 'main=StarboardMain', + ], + 'cflags': [ + # To link into a shared library on Linux and similar platforms, + # the compiler must be told to generate Position Independent Code. + # This appears to cause errors when linking the code statically, + # however. + '-fPIC', + ], + }], + ], + }], ['posix_emulation_target_type == "shared_library"', { 'defines': [ '__LB_BASE_SHARED__=1', @@ -294,6 +333,10 @@ '<(DEPTH)/lbshell/src/platform/<(target_arch)/posix_emulation/lb_shell', # headers that we don't need, but should exist somewhere in the path: '<(DEPTH)/lbshell/src/platform/<(target_arch)/posix_emulation/place_holders', + # TODO: This is needed to support the option to include + # posix_emulation.h to all compiled source files. This dependency + # should be refactored and removed. + '<(DEPTH)/lbshell/src', ], }], # OS == "lb_shell" ['OS == "starboard"', { @@ -421,10 +464,13 @@ 'cobalt_copy_debug_console': 1, 'cobalt_copy_test_data': 1, 'enable_about_scheme': 1, + 'enable_fake_microphone': 1, 'enable_file_scheme': 1, 'enable_network_logging': 1, 'enable_remote_debugging%': 1, 'enable_screenshot': 1, + 'enable_webdriver%': 1, + 'sb_allows_memory_tracking': 1, }, }, { @@ -432,10 +478,13 @@ 'cobalt_copy_debug_console': 0, 'cobalt_copy_test_data': 0, 'enable_about_scheme': 0, + 'enable_fake_microphone': 0, 'enable_file_scheme': 0, 'enable_network_logging': 0, 'enable_remote_debugging%': 0, 'enable_screenshot': 0, + 'enable_webdriver': 0, + 'sb_allows_memory_tracking': 0, }, }], ],
diff --git a/src/cobalt/build/config/win.gypi b/src/cobalt/build/config/win.gypi index 6a8cab4..8504e8b 100644 --- a/src/cobalt/build/config/win.gypi +++ b/src/cobalt/build/config/win.gypi
@@ -34,6 +34,9 @@ # there for acceptable values for this variable. 'javascript_engine': 'javascriptcore', + # Webdriver won't compile on MSVC because of UTF8 string constant issues + 'enable_webdriver': 0, + # Compile with "PREfast" on by default. 'static_analysis%': 'true',
diff --git a/src/cobalt/build/gyp_cobalt b/src/cobalt/build/gyp_cobalt index 0e60bb9..9ef3934 100755 --- a/src/cobalt/build/gyp_cobalt +++ b/src/cobalt/build/gyp_cobalt
@@ -208,7 +208,7 @@ 'cobalt_fastbuild': os.environ.get('LB_FASTBUILD', 0), 'cobalt_version': gyp_utils.GetBuildNumber(), 'host_os': _GetHostOS(), - 'CC': os.environ.get('CC', ''), + 'CC_HOST': os.environ.get('CC_HOST', os.environ.get('CC','')), } if self.platform_config.IsStarboard(): variables['OS'] = 'starboard' @@ -218,6 +218,7 @@ full_starboard_path = platforms[platform] assert full_starboard_path[:len(source_tree_dir)] == source_tree_dir starboard_path = full_starboard_path[len(source_tree_dir) + 1:] + starboard_path.replace(os.sep, '/') assert starboard_path[0] not in [ os.sep, os.altsep ] variables['starboard_path'] = starboard_path _AppendVariables(variables, self.common_args)
diff --git a/src/cobalt/build/save_build_id.py b/src/cobalt/build/save_build_id.py index 1f79d60..63ab937 100755 --- a/src/cobalt/build/save_build_id.py +++ b/src/cobalt/build/save_build_id.py
@@ -14,13 +14,14 @@ # limitations under the License. """Calculates the current Build ID and writes it to 'build.id'.""" +import argparse import logging import os import sys +import textwrap import gyp_utils - _SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__)) _BUILD_ID_PATH = gyp_utils.BUILD_ID_PATH @@ -32,12 +33,28 @@ def main(): logging.basicConfig(level=logging.WARNING, format='%(message)s') + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + description=textwrap.dedent(__doc__)) + + parser.add_argument( + '--delete', + '-d', + action='store_true', + default=False, + help='Delete build.id file.') + + options = parser.parse_args() + # Update the build id to the latest, even if one is already set. try: os.unlink(_BUILD_ID_PATH) except OSError: pass + if options.delete: + return 0 + build_id = gyp_utils.GetBuildNumber() if not build_id: logging.error('Unable to retrieve build id.')
diff --git a/src/cobalt/content/fonts/10megabytes/fonts.xml b/src/cobalt/content/fonts/10megabytes/fonts.xml index c0ffb0a..d066d4a 100644 --- a/src/cobalt/content/fonts/10megabytes/fonts.xml +++ b/src/cobalt/content/fonts/10megabytes/fonts.xml
@@ -1,4 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> +<familyset version="1"> <!-- NOTE: Families with a "fallback" value of "true" are added to the fallback list, regardless of whether or not they are named. Fallback fonts are chosen @@ -13,10 +14,9 @@ indexed, and each page contains 256 characters, so character 1000 would be contained within page 3. --> -<familyset version="1"> <!-- first font is default --> <family name="sans-serif"> - <font weight="400" style="normal">Roboto-Regular.ttf</font> + <font font_name="Roboto Regular" postscript_name="Roboto-Regular" style="normal" weight="400">Roboto-Regular.ttf</font> </family> <!-- Note that aliases must come after the fonts they reference. --> <alias name="arial" to="sans-serif" /> @@ -27,187 +27,187 @@ <alias name="courier" to="serif-monospace" /> <alias name="courier new" to="serif-monospace" /> <family name="sans-serif-smallcaps"> - <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font> + <font font_name="Carrois Gothic SC" postscript_name="CarroisGothicSC-Regular" style="normal" weight="400">CarroisGothicSC-Regular.ttf</font> </family> <!-- fallback fonts --> - <family name="Noto Naskh Arabic UI" fallback="true" pages="0,6-8,32,37,46,251-254"> - <font weight="400" style="normal">NotoNaskhArabicUI-Regular.ttf</font> + <family fallback="true" name="Noto Naskh Arabic UI" pages="0,6-8,32,37,46,251-254"> + <font font_name="Noto Naskh Arabic UI" postscript_name="NotoNaskhArabicUI" style="normal" weight="400">NotoNaskhArabicUI-Regular.ttf</font> </family> <family fallback="true" pages="0,18-19,45,171,254"> - <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font> + <font font_name="Noto Sans Ethiopic" postscript_name="NotoSansEthiopic" style="normal" weight="400">NotoSansEthiopic-Regular.ttf</font> </family> <family fallback="true" pages="0,5,32,37,251,254"> - <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font> + <font font_name="Noto Sans Hebrew" postscript_name="NotoSansHebrew" style="normal" weight="400">NotoSansHebrew-Regular.ttf</font> </family> <family fallback="true" pages="0,2-3,14,32,37,254"> - <font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font> + <font font_name="Noto Sans Thai UI" postscript_name="NotoSansThaiUI" style="normal" weight="400">NotoSansThaiUI-Regular.ttf</font> </family> <family fallback="true" pages="0,5,251,254"> - <font weight="400" style="normal">NotoSansArmenian-Regular.ttf</font> + <font font_name="Noto Sans Armenian" postscript_name="NotoSansArmenian" style="normal" weight="400">NotoSansArmenian-Regular.ttf</font> </family> <family fallback="true" pages="0,5,16,45,254"> - <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font> + <font font_name="Noto Sans Georgian" postscript_name="NotoSansGeorgian" style="normal" weight="400">NotoSansGeorgian-Regular.ttf</font> </family> <family fallback="true" pages="0,2,9,28,32,34,37,168,254"> - <font weight="400" style="normal">NotoSansDevanagariUI-Regular.ttf</font> + <font font_name="Noto Sans Devanagari UI" postscript_name="NotoSansDevanagariUI" style="normal" weight="400">NotoSansDevanagariUI-Regular.ttf</font> </family> <!-- Gujarati should come after Devanagari --> <family fallback="true" pages="0,9-10,32,34,37,168,254"> - <font weight="400" style="normal">NotoSansGujaratiUI-Regular.ttf</font> + <font font_name="Noto Sans Gujarati UI" postscript_name="NotoSansGujaratiUI" style="normal" weight="400">NotoSansGujaratiUI-Regular.ttf</font> </family> <!-- Gurmukhi should come after Devanagari --> <family fallback="true" pages="0,9-10,32,34,37-38,168,254"> - <font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf</font> + <font font_name="Noto Sans Gurmukhi UI" postscript_name="NotoSansGurmukhiUI" style="normal" weight="400">NotoSansGurmukhiUI-Regular.ttf</font> </family> <family fallback="true" pages="0,9,11,32,34,37,254"> - <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font> + <font font_name="Noto Sans Tamil UI" postscript_name="NotoSansTamilUI" style="normal" weight="400">NotoSansTamilUI-Regular.ttf</font> </family> <family fallback="true" pages="0,3,9,13,32,34,37,254"> - <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font> + <font font_name="Noto Sans Malayalam UI" postscript_name="NotoSansMalayalamUI" style="normal" weight="400">NotoSansMalayalamUI-Regular.ttf</font> </family> <family fallback="true" pages="0,9,32,34,37,254"> - <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font> + <font font_name="Noto Sans Bengali UI" postscript_name="NotoSansBengaliUI" style="normal" weight="400">NotoSansBengaliUI-Regular.ttf</font> </family> <family fallback="true" pages="0,9,12,32,34,37,254"> - <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font> + <font font_name="Noto Sans Telugu UI" postscript_name="NotoSansTeluguUI" style="normal" weight="400">NotoSansTeluguUI-Regular.ttf</font> </family> <family fallback="true" pages="0,9,12,32,34,37,254"> - <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font> + <font font_name="Noto Sans Kannada UI" postscript_name="NotoSansKannadaUI" style="normal" weight="400">NotoSansKannadaUI-Regular.ttf</font> </family> <family fallback="true" pages="0,9,11,32,34,37,254"> - <font weight="400" style="normal">NotoSansOriyaUI-Regular.ttf</font> + <font font_name="Noto Sans Oriya UI" postscript_name="NotoSansOriyaUI" style="normal" weight="400">NotoSansOriyaUI-Regular.ttf</font> </family> <family fallback="true" pages="0,9,13,32,34,37,254"> - <font weight="400" style="normal">NotoSansSinhala-Regular.ttf</font> + <font font_name="Noto Sans Sinhala" postscript_name="NotoSansSinhala" style="normal" weight="400">NotoSansSinhala-Regular.ttf</font> </family> <family fallback="true" pages="0,23,25,32,37"> - <font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font> + <font font_name="Noto Sans Khmer UI" postscript_name="NotoSansKhmerUI" style="normal" weight="400">NotoSansKhmerUI-Regular.ttf</font> </family> <family fallback="true" pages="0,3,14,32,37"> - <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf</font> + <font font_name="Noto Sans Lao UI" postscript_name="NotoSansLaoUI" style="normal" weight="400">NotoSansLaoUI-Regular.ttf</font> </family> <family fallback="true" pages="0,6-7,32,37,253-254"> - <font weight="400" style="normal">NotoSansThaana-Regular.ttf</font> + <font font_name="Noto Sans Thaana" postscript_name="NotoSansThaana" style="normal" weight="400">NotoSansThaana-Regular.ttf</font> </family> <family fallback="true" pages="0,3,170"> - <font weight="400" style="normal">NotoSansCham-Regular.ttf</font> + <font font_name="Noto Sans Cham" postscript_name="NotoSansCham" style="normal" weight="400">NotoSansCham-Regular.ttf</font> </family> <family fallback="true" pages="0,27,32,37,254"> - <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font> + <font font_name="Noto Sans Balinese" postscript_name="NotoSansBalinese" style="normal" weight="400">NotoSansBalinese-Regular.ttf</font> </family> <family fallback="true" pages="0,166,254,360-362"> - <font weight="400" style="normal">NotoSansBamum-Regular.ttf</font> + <font font_name="Noto Sans Bamum" postscript_name="NotoSansBamum" style="normal" weight="400">NotoSansBamum-Regular.ttf</font> </family> <family fallback="true" pages="0,27,254"> - <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font> + <font font_name="Noto Sans Batak" postscript_name="NotoSansBatak" style="normal" weight="400">NotoSansBatak-Regular.ttf</font> </family> <family fallback="true" pages="0,26,32,169,254"> - <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font> + <font font_name="Noto Sans Buginese" postscript_name="NotoSansBuginese" style="normal" weight="400">NotoSansBuginese-Regular.ttf</font> </family> <family fallback="true" pages="0,23,254"> - <font weight="400" style="normal">NotoSansBuhid-Regular.ttf</font> + <font font_name="Noto Sans Buhid" postscript_name="NotoSansBuhid" style="normal" weight="400">NotoSansBuhid-Regular.ttf</font> </family> <family fallback="true" pages="0-3,20-22,24,254"> - <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font> + <font font_name="Noto Sans Canadian Aboriginal" postscript_name="NotoSansCanadianAboriginal" style="normal" weight="400">NotoSansCanadianAboriginal-Regular.ttf</font> </family> <family fallback="true" pages="0,19,254"> - <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> + <font font_name="Noto Sans Cherokee" postscript_name="NotoSansCherokee" style="normal" weight="400">NotoSansCherokee-Regular.ttf</font> </family> <family fallback="true" pages="0-3,29,44,254"> - <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font> + <font font_name="Noto Sans Coptic" postscript_name="NotoSansCoptic" style="normal" weight="400">NotoSansCoptic-Regular.ttf</font> </family> <family fallback="true" pages="0,44,254"> - <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font> + <font font_name="Noto Sans Glagolitic" postscript_name="NotoSansGlagolitic" style="normal" weight="400">NotoSansGlagolitic-Regular.ttf</font> </family> <family fallback="true" pages="0,23,254"> - <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font> + <font font_name="Noto Sans Hanunoo" postscript_name="NotoSansHanunoo" style="normal" weight="400">NotoSansHanunoo-Regular.ttf</font> </family> <family fallback="true" pages="0,32,37,169,254"> - <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font> + <font font_name="Noto Sans Javanese" postscript_name="NotoSansJavanese" style="normal" weight="400">NotoSansJavanese-Regular.ttf</font> </family> <family fallback="true" pages="0,169,254"> - <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font> + <font font_name="Noto Sans Kayah Li" postscript_name="NotoSansKayahLi" style="normal" weight="400">NotoSansKayahLi-Regular.ttf</font> </family> <family fallback="true" pages="0,28,37,254"> - <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font> + <font font_name="Noto Sans Lepcha" postscript_name="NotoSansLepcha" style="normal" weight="400">NotoSansLepcha-Regular.ttf</font> </family> <family fallback="true" pages="0,9,25,254"> - <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font> + <font font_name="Noto Sans Limbu" postscript_name="NotoSansLimbu" style="normal" weight="400">NotoSansLimbu-Regular.ttf</font> </family> <family fallback="true" pages="0,2,164,254"> - <font weight="400" style="normal">NotoSansLisu-Regular.ttf</font> + <font font_name="Noto Sans Lisu" postscript_name="NotoSansLisu" style="normal" weight="400">NotoSansLisu-Regular.ttf</font> </family> <family fallback="true" pages="0,6,8,254"> - <font weight="400" style="normal">NotoSansMandaic-Regular.ttf</font> + <font font_name="Noto Sans Mandaic" postscript_name="NotoSansMandaic" style="normal" weight="400">NotoSansMandaic-Regular.ttf</font> </family> <family fallback="true" pages="0,170-171,254"> - <font weight="400" style="normal">NotoSansMeeteiMayek-Regular.ttf</font> + <font font_name="Noto Sans Meetei Mayek" postscript_name="NotoSansMeeteiMayek" style="normal" weight="400">NotoSansMeeteiMayek-Regular.ttf</font> </family> <family fallback="true" pages="0,25,254"> - <font weight="400" style="normal">NotoSansNewTaiLue-Regular.ttf</font> + <font font_name="Noto Sans New Tai Lue" postscript_name="NotoSansNewTaiLue" style="normal" weight="400">NotoSansNewTaiLue-Regular.ttf</font> </family> <family fallback="true" pages="0,6-7,32,46,253-254"> - <font weight="400" style="normal">NotoSansNKo-Regular.ttf</font> + <font font_name="Noto Sans NKo" postscript_name="NotoSansNKo" style="normal" weight="400">NotoSansNKo-Regular.ttf</font> </family> <family fallback="true" pages="0,28,254"> - <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font> + <font font_name="Noto Sans Ol Chiki" postscript_name="NotoSansOlChiki" style="normal" weight="400">NotoSansOlChiki-Regular.ttf</font> </family> <family fallback="true" pages="0,169,254"> - <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font> + <font font_name="Noto Sans Rejang" postscript_name="NotoSansRejang" style="normal" weight="400">NotoSansRejang-Regular.ttf</font> </family> <family fallback="true" pages="0,32,37,168,254"> - <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font> + <font font_name="Noto Sans Saurashtra" postscript_name="NotoSansSaurashtra" style="normal" weight="400">NotoSansSaurashtra-Regular.ttf</font> </family> <family fallback="true" pages="0,27-28,254"> - <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font> + <font font_name="Noto Sans Sundanese" postscript_name="NotoSansSundanese" style="normal" weight="400">NotoSansSundanese-Regular.ttf</font> </family> <family fallback="true" pages="0,9,32,37,168,254"> - <font weight="400" style="normal">NotoSansSylotiNagri-Regular.ttf</font> + <font font_name="Noto Sans Syloti Nagri" postscript_name="NotoSansSylotiNagri" style="normal" weight="400">NotoSansSylotiNagri-Regular.ttf</font> </family> <family fallback="true" pages="0,3,6-7,32,34,37-38,254"> - <font weight="400" style="normal">NotoSansSyriacEstrangela-Regular.ttf</font> + <font font_name="Noto Sans Syriac Estrangela" postscript_name="NotoSansSyriacEstrangela" style="normal" weight="400">NotoSansSyriacEstrangela-Regular.ttf</font> </family> <family fallback="true" pages="0,23,254"> - <font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font> + <font font_name="Noto Sans Tagbanwa" postscript_name="NotoSansTagbanwa" style="normal" weight="400">NotoSansTagbanwa-Regular.ttf</font> </family> <family fallback="true" pages="0,26,32,34,37,254"> - <font weight="400" style="normal">NotoSansTaiTham-Regular.ttf</font> + <font font_name="Noto Sans Tai Tham" postscript_name="NotoSansTaiTham" style="normal" weight="400">NotoSansTaiTham-Regular.ttf</font> </family> <family fallback="true" pages="0,32,37,167,170,254"> - <font weight="400" style="normal">NotoSansTaiViet-Regular.ttf</font> + <font font_name="Noto Sans Tai Viet" postscript_name="NotoSansTaiViet" style="normal" weight="400">NotoSansTaiViet-Regular.ttf</font> </family> <family fallback="true" pages="0,15,32,37,254"> - <font weight="400" style="normal">NotoSansTibetan-Regular.ttf</font> + <font font_name="Noto Sans Tibetan" postscript_name="NotoSansTibetan" style="normal" weight="400">NotoSansTibetan-Regular.ttf</font> </family> <family fallback="true" pages="0,3,32,45,254"> - <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font> + <font font_name="Noto Sans Tifinagh" postscript_name="NotoSansTifinagh" style="normal" weight="400">NotoSansTifinagh-Regular.ttf</font> </family> <family fallback="true" pages="0,165-166,254"> - <font weight="400" style="normal">NotoSansVai-Regular.ttf</font> + <font font_name="Noto Sans Vai" postscript_name="NotoSansVai" style="normal" weight="400">NotoSansVai-Regular.ttf</font> </family> <family fallback="true" pages="0,160-164,254"> - <font weight="400" style="normal">NotoSansYi-Regular.ttf</font> + <font font_name="Noto Sans Yi" postscript_name="NotoSansYi" style="normal" weight="400">NotoSansYi-Regular.ttf</font> </family> <family fallback="true" pages="32-43"> - <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> + <font font_name="Noto Sans Symbols" postscript_name="NotoSansSymbols" style="normal" weight="400">NotoSansSymbols-Regular-Subsetted.ttf</font> </family> <family fallback="true" lang="ja" pages="0,32,34-35,46-159,249-250,254-255,498,512-523,525-527,530-538,540-543,545-547,550,552,554-559,561,563-568,570,572-573,575-579,582-584,586-594,596-608,610-612,614-618,620,622-625,627-628,630-631,633-638,640,642-646,649-655,658,660-664,666,669-678,681,695-696,760-761"> - <font weight="400" style="normal">NotoSansJP-Regular.otf</font> + <font font_name="Noto Sans JP Regular" postscript_name="NotoSansJP-Regular" style="normal" weight="400">NotoSansJP-Regular.otf</font> </family> <family fallback="true" pages="0,32-33,35-39,41,43,48,50,224,254-255,496-502,4068"> - <font weight="400" style="normal">NotoEmoji-Regular.ttf</font> + <font font_name="Noto Emoji" postscript_name="NotoEmoji" style="normal" weight="400">NotoEmoji-Regular.ttf</font> </family> <family fallback="true" pages="0,14,17,32,48-51,77-159,172-215,249-250,254-255,260"> - <font weight="400" style="normal">DroidSansFallback.ttf</font> + <font font_name="Droid Sans Fallback" postscript_name="DroidSansFallback" style="normal" weight="400">DroidSansFallback.ttf</font> </family> <!-- Tai Le and Mongolian are intentionally kept last, to make sure they don't override the East Asian punctuation for Chinese. --> <family fallback="true" pages="0,16,25,48,254"> - <font weight="400" style="normal">NotoSansTaiLe-Regular.ttf</font> + <font font_name="Noto Sans Tai Le" postscript_name="NotoSansTaiLe" style="normal" weight="400">NotoSansTaiLe-Regular.ttf</font> </family> <family fallback="true" pages="0,24,32,36-37,48,254"> - <font weight="400" style="normal">NotoSansMongolian-Regular.ttf</font> + <font font_name="Noto Sans Mongolian" postscript_name="NotoSansMongolian" style="normal" weight="400">NotoSansMongolian-Regular.ttf</font> </family> </familyset>
diff --git a/src/cobalt/content/fonts/minimal/fonts.xml b/src/cobalt/content/fonts/minimal/fonts.xml index 1381b81..bd40ee3 100644 --- a/src/cobalt/content/fonts/minimal/fonts.xml +++ b/src/cobalt/content/fonts/minimal/fonts.xml
@@ -1,11 +1,25 @@ <?xml version="1.0" encoding="utf-8"?> <familyset version="1"> +<!-- + NOTE: Families with a "fallback" value of "true" are added to the fallback + list, regardless of whether or not they are named. Fallback fonts are chosen + based on a match: full BCP-47 language tag including script, then just + language, and finally order (the first font containing the glyph). Order of + appearance is also the tiebreaker for weight matching. + + The pages attribute indicates which character pages are contained within + the font. It is used with character fallback to allow the system to quickly + determine that a character cannot appear in a font without requiring the + full character map to be loaded into memory. Character pages are zero + indexed, and each page contains 256 characters, so character 1000 would be + contained within page 3. +--> <!-- Ideally, this font should only be used if there are no other fonts in our final image. --> <family name="Minimal Roboto"> - <font weight="400" style="normal">MinimalRoboto.ttf</font> + <font font_name="Minimal Roboto" postscript_name="Minimal Roboto" style="normal" weight="400">MinimalRoboto.ttf</font> </family> <family name="sans-serif-smallcaps"> - <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font> + <font font_name="Carrois Gothic SC" postscript_name="CarroisGothicSC-Regular" style="normal" weight="400">CarroisGothicSC-Regular.ttf</font> </family> </familyset>
diff --git a/src/cobalt/content/fonts/unlimited/fonts.xml b/src/cobalt/content/fonts/unlimited/fonts.xml index 3e1ec53..25426c3 100644 --- a/src/cobalt/content/fonts/unlimited/fonts.xml +++ b/src/cobalt/content/fonts/unlimited/fonts.xml
@@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<familyset version="1"> <!-- NOTE: Families with a "fallback" value of "true" are added to the fallback list, regardless of whether or not they are named. Fallback fonts are chosen @@ -13,13 +13,12 @@ indexed, and each page contains 256 characters, so character 1000 would be contained within page 3. --> -<familyset version="1"> <!-- first font is default --> <family name="sans-serif"> - <font weight="400" style="normal">Roboto-Regular.ttf</font> - <font weight="400" style="italic">Roboto-Italic.ttf</font> - <font weight="700" style="normal">Roboto-Bold.ttf</font> - <font weight="700" style="italic">Roboto-BoldItalic.ttf</font> + <font font_name="Roboto Regular" postscript_name="Roboto-Regular" style="normal" weight="400">Roboto-Regular.ttf</font> + <font font_name="Roboto Italic" postscript_name="Roboto-Italic" style="italic" weight="400">Roboto-Italic.ttf</font> + <font font_name="Roboto Bold" postscript_name="Roboto-Bold" style="normal" weight="700">Roboto-Bold.ttf</font> + <font font_name="Roboto Bold Italic" postscript_name="Roboto-BoldItalic" style="italic" weight="700">Roboto-BoldItalic.ttf</font> </family> <!-- Note that aliases must come after the fonts they reference. --> <alias name="arial" to="sans-serif" /> @@ -30,13 +29,13 @@ <!-- Ideally, this font should only be used if there are no other fonts in our final image. --> <family name="Minimal Roboto"> - <font weight="400" style="normal">MinimalRoboto.ttf</font> + <font font_name="Minimal Roboto" postscript_name="Minimal Roboto" style="normal" weight="400">MinimalRoboto.ttf</font> </family> <family name="serif"> - <font weight="400" style="normal">NotoSerif-Regular.ttf</font> - <font weight="400" style="italic">NotoSerif-Italic.ttf</font> - <font weight="700" style="normal">NotoSerif-Bold.ttf</font> - <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font> + <font font_name="Noto Serif" postscript_name="NotoSerif" style="normal" weight="400">NotoSerif-Regular.ttf</font> + <font font_name="Noto Serif Italic" postscript_name="NotoSerif-Italic" style="italic" weight="400">NotoSerif-Italic.ttf</font> + <font font_name="Noto Serif Bold" postscript_name="NotoSerif-Bold" style="normal" weight="700">NotoSerif-Bold.ttf</font> + <font font_name="Noto Serif Bold Italic" postscript_name="NotoSerif-BoldItalic" style="italic" weight="700">NotoSerif-BoldItalic.ttf</font> </family> <alias name="times" to="serif" /> <alias name="times new roman" to="serif" /> @@ -47,243 +46,243 @@ <alias name="fantasy" to="serif" /> <alias name="ITC Stone Serif" to="serif" /> <family name="monospace"> - <font weight="400" style="normal">DroidSansMono.ttf</font> + <font font_name="Droid Sans Mono" postscript_name="DroidSansMono" style="normal" weight="400">DroidSansMono.ttf</font> </family> <alias name="sans-serif-monospace" to="monospace" /> <alias name="monaco" to="monospace" /> <family name="serif-monospace"> - <font weight="400" style="normal">CutiveMono.ttf</font> + <font font_name="Cutive Mono" postscript_name="CutiveMono-Regular" style="normal" weight="400">CutiveMono.ttf</font> </family> <alias name="courier" to="serif-monospace" /> <alias name="courier new" to="serif-monospace" /> <family name="casual"> - <font weight="400" style="normal">ComingSoon.ttf</font> + <font font_name="Coming Soon" postscript_name="ComingSoon" style="normal" weight="400">ComingSoon.ttf</font> </family> <family name="cursive"> - <font weight="400" style="normal">DancingScript-Regular.ttf</font> - <font weight="700" style="normal">DancingScript-Bold.ttf</font> + <font font_name="Dancing Script" postscript_name="DancingScript" style="normal" weight="400">DancingScript-Regular.ttf</font> + <font font_name="Dancing Script Bold" postscript_name="DancingScript-Bold" style="normal" weight="700">DancingScript-Bold.ttf</font> </family> <family name="sans-serif-smallcaps"> - <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font> + <font font_name="Carrois Gothic SC" postscript_name="CarroisGothicSC-Regular" style="normal" weight="400">CarroisGothicSC-Regular.ttf</font> </family> <!-- fallback fonts --> - <family name="Noto Naskh Arabic UI" fallback="true" pages="0,6-8,32,37,46,251-254"> - <font weight="400" style="normal">NotoNaskhArabicUI-Regular.ttf</font> - <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font> + <family fallback="true" name="Noto Naskh Arabic UI" pages="0,6-8,32,37,46,251-254"> + <font font_name="Noto Naskh Arabic UI" postscript_name="NotoNaskhArabicUI" style="normal" weight="400">NotoNaskhArabicUI-Regular.ttf</font> + <font font_name="Noto Naskh Arabic UI Bold" postscript_name="NotoNaskhArabicUI-Bold" style="normal" weight="700">NotoNaskhArabicUI-Bold.ttf</font> </family> <family fallback="true" pages="0,18-19,45,171,254"> - <font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font> - <font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font> + <font font_name="Noto Sans Ethiopic" postscript_name="NotoSansEthiopic" style="normal" weight="400">NotoSansEthiopic-Regular.ttf</font> + <font font_name="Noto Sans Ethiopic Bold" postscript_name="NotoSansEthiopic-Bold" style="normal" weight="700">NotoSansEthiopic-Bold.ttf</font> </family> <family fallback="true" pages="0,5,32,37,251,254"> - <font weight="400" style="normal">NotoSansHebrew-Regular.ttf</font> - <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font> + <font font_name="Noto Sans Hebrew" postscript_name="NotoSansHebrew" style="normal" weight="400">NotoSansHebrew-Regular.ttf</font> + <font font_name="Noto Sans Hebrew Bold" postscript_name="NotoSansHebrew-Bold" style="normal" weight="700">NotoSansHebrew-Bold.ttf</font> </family> <family fallback="true" pages="0,2-3,14,32,37,254"> - <font weight="400" style="normal">NotoSansThaiUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font> + <font font_name="Noto Sans Thai UI" postscript_name="NotoSansThaiUI" style="normal" weight="400">NotoSansThaiUI-Regular.ttf</font> + <font font_name="Noto Sans Thai UI Bold" postscript_name="NotoSansThaiUI-Bold" style="normal" weight="700">NotoSansThaiUI-Bold.ttf</font> </family> <family fallback="true" pages="0,5,251,254"> - <font weight="400" style="normal">NotoSansArmenian-Regular.ttf</font> - <font weight="700" style="normal">NotoSansArmenian-Bold.ttf</font> + <font font_name="Noto Sans Armenian" postscript_name="NotoSansArmenian" style="normal" weight="400">NotoSansArmenian-Regular.ttf</font> + <font font_name="Noto Sans Armenian Bold" postscript_name="NotoSansArmenian-Bold" style="normal" weight="700">NotoSansArmenian-Bold.ttf</font> </family> <family fallback="true" pages="0,5,16,45,254"> - <font weight="400" style="normal">NotoSansGeorgian-Regular.ttf</font> - <font weight="700" style="normal">NotoSansGeorgian-Bold.ttf</font> + <font font_name="Noto Sans Georgian" postscript_name="NotoSansGeorgian" style="normal" weight="400">NotoSansGeorgian-Regular.ttf</font> + <font font_name="Noto Sans Georgian Bold" postscript_name="NotoSansGeorgian-Bold" style="normal" weight="700">NotoSansGeorgian-Bold.ttf</font> </family> <family fallback="true" pages="0,2,9,28,32,34,37,168,254"> - <font weight="400" style="normal">NotoSansDevanagariUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansDevanagariUI-Bold.ttf</font> + <font font_name="Noto Sans Devanagari UI" postscript_name="NotoSansDevanagariUI" style="normal" weight="400">NotoSansDevanagariUI-Regular.ttf</font> + <font font_name="Noto Sans Devanagari UI Bold" postscript_name="NotoSansDevanagariUI-Bold" style="normal" weight="700">NotoSansDevanagariUI-Bold.ttf</font> </family> <!-- Gujarati should come after Devanagari --> <family fallback="true" pages="0,9-10,32,34,37,168,254"> - <font weight="400" style="normal">NotoSansGujaratiUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font> + <font font_name="Noto Sans Gujarati UI" postscript_name="NotoSansGujaratiUI" style="normal" weight="400">NotoSansGujaratiUI-Regular.ttf</font> + <font font_name="Noto Sans Gujarati UI Bold" postscript_name="NotoSansGujaratiUI-Bold" style="normal" weight="700">NotoSansGujaratiUI-Bold.ttf</font> </family> <!-- Gurmukhi should come after Devanagari --> <family fallback="true" pages="0,9-10,32,34,37-38,168,254"> - <font weight="400" style="normal">NotoSansGurmukhiUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansGurmukhiUI-Bold.ttf</font> + <font font_name="Noto Sans Gurmukhi UI" postscript_name="NotoSansGurmukhiUI" style="normal" weight="400">NotoSansGurmukhiUI-Regular.ttf</font> + <font font_name="Noto Sans Gurmukhi UI Bold" postscript_name="NotoSansGurmukhiUI-Bold" style="normal" weight="700">NotoSansGurmukhiUI-Bold.ttf</font> </family> <family fallback="true" pages="0,9,11,32,34,37,254"> - <font weight="400" style="normal">NotoSansTamilUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansTamilUI-Bold.ttf</font> + <font font_name="Noto Sans Tamil UI" postscript_name="NotoSansTamilUI" style="normal" weight="400">NotoSansTamilUI-Regular.ttf</font> + <font font_name="Noto Sans Tamil UI Bold" postscript_name="NotoSansTamilUI-Bold" style="normal" weight="700">NotoSansTamilUI-Bold.ttf</font> </family> <family fallback="true" pages="0,3,9,13,32,34,37,254"> - <font weight="400" style="normal">NotoSansMalayalamUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansMalayalamUI-Bold.ttf</font> + <font font_name="Noto Sans Malayalam UI" postscript_name="NotoSansMalayalamUI" style="normal" weight="400">NotoSansMalayalamUI-Regular.ttf</font> + <font font_name="Noto Sans Malayalam UI Bold" postscript_name="NotoSansMalayalamUI-Bold" style="normal" weight="700">NotoSansMalayalamUI-Bold.ttf</font> </family> <family fallback="true" pages="0,9,32,34,37,254"> - <font weight="400" style="normal">NotoSansBengaliUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansBengaliUI-Bold.ttf</font> + <font font_name="Noto Sans Bengali UI" postscript_name="NotoSansBengaliUI" style="normal" weight="400">NotoSansBengaliUI-Regular.ttf</font> + <font font_name="Noto Sans Bengali UI Bold" postscript_name="NotoSansBengaliUI-Bold" style="normal" weight="700">NotoSansBengaliUI-Bold.ttf</font> </family> <family fallback="true" pages="0,9,12,32,34,37,254"> - <font weight="400" style="normal">NotoSansTeluguUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansTeluguUI-Bold.ttf</font> + <font font_name="Noto Sans Telugu UI" postscript_name="NotoSansTeluguUI" style="normal" weight="400">NotoSansTeluguUI-Regular.ttf</font> + <font font_name="Noto Sans Telugu UI Bold" postscript_name="NotoSansTeluguUI-Bold" style="normal" weight="700">NotoSansTeluguUI-Bold.ttf</font> </family> <family fallback="true" pages="0,9,12,32,34,37,254"> - <font weight="400" style="normal">NotoSansKannadaUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansKannadaUI-Bold.ttf</font> + <font font_name="Noto Sans Kannada UI" postscript_name="NotoSansKannadaUI" style="normal" weight="400">NotoSansKannadaUI-Regular.ttf</font> + <font font_name="Noto Sans Kannada UI Bold" postscript_name="NotoSansKannadaUI-Bold" style="normal" weight="700">NotoSansKannadaUI-Bold.ttf</font> </family> <family fallback="true" pages="0,9,11,32,34,37,254"> - <font weight="400" style="normal">NotoSansOriyaUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font> + <font font_name="Noto Sans Oriya UI" postscript_name="NotoSansOriyaUI" style="normal" weight="400">NotoSansOriyaUI-Regular.ttf</font> + <font font_name="Noto Sans Oriya UI Bold" postscript_name="NotoSansOriyaUI-Bold" style="normal" weight="700">NotoSansOriyaUI-Bold.ttf</font> </family> <family fallback="true" pages="0,9,13,32,34,37,254"> - <font weight="400" style="normal">NotoSansSinhala-Regular.ttf</font> - <font weight="700" style="normal">NotoSansSinhala-Bold.ttf</font> + <font font_name="Noto Sans Sinhala" postscript_name="NotoSansSinhala" style="normal" weight="400">NotoSansSinhala-Regular.ttf</font> + <font font_name="Noto Sans Sinhala Bold" postscript_name="NotoSansSinhala-Bold" style="normal" weight="700">NotoSansSinhala-Bold.ttf</font> </family> <family fallback="true" pages="0,23,25,32,37"> - <font weight="400" style="normal">NotoSansKhmerUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font> + <font font_name="Noto Sans Khmer UI" postscript_name="NotoSansKhmerUI" style="normal" weight="400">NotoSansKhmerUI-Regular.ttf</font> + <font font_name="Noto Sans Khmer UI Bold" postscript_name="NotoSansKhmerUI-Bold" style="normal" weight="700">NotoSansKhmerUI-Bold.ttf</font> </family> <family fallback="true" pages="0,3,14,32,37"> - <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font> + <font font_name="Noto Sans Lao UI" postscript_name="NotoSansLaoUI" style="normal" weight="400">NotoSansLaoUI-Regular.ttf</font> + <font font_name="Noto Sans Lao UI Bold" postscript_name="NotoSansLaoUI-Bold" style="normal" weight="700">NotoSansLaoUI-Bold.ttf</font> </family> <family fallback="true" pages="0,16,32,37,169-170,254"> - <font weight="400" style="normal">NotoSansMyanmarUI-Regular.ttf</font> - <font weight="700" style="normal">NotoSansMyanmarUI-Bold.ttf</font> + <font font_name="Noto Sans Myanmar UI" postscript_name="NotoSansMyanmarUI" style="normal" weight="400">NotoSansMyanmarUI-Regular.ttf</font> + <font font_name="Noto Sans Myanmar UI Bold" postscript_name="NotoSansMyanmarUI-Bold" style="normal" weight="700">NotoSansMyanmarUI-Bold.ttf</font> </family> <family fallback="true" pages="0,6-7,32,37,253-254"> - <font weight="400" style="normal">NotoSansThaana-Regular.ttf</font> - <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font> + <font font_name="Noto Sans Thaana" postscript_name="NotoSansThaana" style="normal" weight="400">NotoSansThaana-Regular.ttf</font> + <font font_name="Noto Sans Thaana Bold" postscript_name="NotoSansThaana-Bold" style="normal" weight="700">NotoSansThaana-Bold.ttf</font> </family> <family fallback="true" pages="0,3,170"> - <font weight="400" style="normal">NotoSansCham-Regular.ttf</font> - <font weight="700" style="normal">NotoSansCham-Bold.ttf</font> + <font font_name="Noto Sans Cham" postscript_name="NotoSansCham" style="normal" weight="400">NotoSansCham-Regular.ttf</font> + <font font_name="Noto Sans Cham Bold" postscript_name="NotoSansCham-Bold" style="normal" weight="700">NotoSansCham-Bold.ttf</font> </family> <family fallback="true" pages="0,27,32,37,254"> - <font weight="400" style="normal">NotoSansBalinese-Regular.ttf</font> + <font font_name="Noto Sans Balinese" postscript_name="NotoSansBalinese" style="normal" weight="400">NotoSansBalinese-Regular.ttf</font> </family> <family fallback="true" pages="0,166,254,360-362"> - <font weight="400" style="normal">NotoSansBamum-Regular.ttf</font> + <font font_name="Noto Sans Bamum" postscript_name="NotoSansBamum" style="normal" weight="400">NotoSansBamum-Regular.ttf</font> </family> <family fallback="true" pages="0,27,254"> - <font weight="400" style="normal">NotoSansBatak-Regular.ttf</font> + <font font_name="Noto Sans Batak" postscript_name="NotoSansBatak" style="normal" weight="400">NotoSansBatak-Regular.ttf</font> </family> <family fallback="true" pages="0,26,32,169,254"> - <font weight="400" style="normal">NotoSansBuginese-Regular.ttf</font> + <font font_name="Noto Sans Buginese" postscript_name="NotoSansBuginese" style="normal" weight="400">NotoSansBuginese-Regular.ttf</font> </family> <family fallback="true" pages="0,23,254"> - <font weight="400" style="normal">NotoSansBuhid-Regular.ttf</font> + <font font_name="Noto Sans Buhid" postscript_name="NotoSansBuhid" style="normal" weight="400">NotoSansBuhid-Regular.ttf</font> </family> <family fallback="true" pages="0-3,20-22,24,254"> - <font weight="400" style="normal">NotoSansCanadianAboriginal-Regular.ttf</font> + <font font_name="Noto Sans Canadian Aboriginal" postscript_name="NotoSansCanadianAboriginal" style="normal" weight="400">NotoSansCanadianAboriginal-Regular.ttf</font> </family> <family fallback="true" pages="0,19,254"> - <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font> + <font font_name="Noto Sans Cherokee" postscript_name="NotoSansCherokee" style="normal" weight="400">NotoSansCherokee-Regular.ttf</font> </family> <family fallback="true" pages="0-3,29,44,254"> - <font weight="400" style="normal">NotoSansCoptic-Regular.ttf</font> + <font font_name="Noto Sans Coptic" postscript_name="NotoSansCoptic" style="normal" weight="400">NotoSansCoptic-Regular.ttf</font> </family> <family fallback="true" pages="0,44,254"> - <font weight="400" style="normal">NotoSansGlagolitic-Regular.ttf</font> + <font font_name="Noto Sans Glagolitic" postscript_name="NotoSansGlagolitic" style="normal" weight="400">NotoSansGlagolitic-Regular.ttf</font> </family> <family fallback="true" pages="0,23,254"> - <font weight="400" style="normal">NotoSansHanunoo-Regular.ttf</font> + <font font_name="Noto Sans Hanunoo" postscript_name="NotoSansHanunoo" style="normal" weight="400">NotoSansHanunoo-Regular.ttf</font> </family> <family fallback="true" pages="0,32,37,169,254"> - <font weight="400" style="normal">NotoSansJavanese-Regular.ttf</font> + <font font_name="Noto Sans Javanese" postscript_name="NotoSansJavanese" style="normal" weight="400">NotoSansJavanese-Regular.ttf</font> </family> <family fallback="true" pages="0,169,254"> - <font weight="400" style="normal">NotoSansKayahLi-Regular.ttf</font> + <font font_name="Noto Sans Kayah Li" postscript_name="NotoSansKayahLi" style="normal" weight="400">NotoSansKayahLi-Regular.ttf</font> </family> <family fallback="true" pages="0,28,37,254"> - <font weight="400" style="normal">NotoSansLepcha-Regular.ttf</font> + <font font_name="Noto Sans Lepcha" postscript_name="NotoSansLepcha" style="normal" weight="400">NotoSansLepcha-Regular.ttf</font> </family> <family fallback="true" pages="0,9,25,254"> - <font weight="400" style="normal">NotoSansLimbu-Regular.ttf</font> + <font font_name="Noto Sans Limbu" postscript_name="NotoSansLimbu" style="normal" weight="400">NotoSansLimbu-Regular.ttf</font> </family> <family fallback="true" pages="0,2,164,254"> - <font weight="400" style="normal">NotoSansLisu-Regular.ttf</font> + <font font_name="Noto Sans Lisu" postscript_name="NotoSansLisu" style="normal" weight="400">NotoSansLisu-Regular.ttf</font> </family> <family fallback="true" pages="0,6,8,254"> - <font weight="400" style="normal">NotoSansMandaic-Regular.ttf</font> + <font font_name="Noto Sans Mandaic" postscript_name="NotoSansMandaic" style="normal" weight="400">NotoSansMandaic-Regular.ttf</font> </family> <family fallback="true" pages="0,170-171,254"> - <font weight="400" style="normal">NotoSansMeeteiMayek-Regular.ttf</font> + <font font_name="Noto Sans Meetei Mayek" postscript_name="NotoSansMeeteiMayek" style="normal" weight="400">NotoSansMeeteiMayek-Regular.ttf</font> </family> <family fallback="true" pages="0,25,254"> - <font weight="400" style="normal">NotoSansNewTaiLue-Regular.ttf</font> + <font font_name="Noto Sans New Tai Lue" postscript_name="NotoSansNewTaiLue" style="normal" weight="400">NotoSansNewTaiLue-Regular.ttf</font> </family> <family fallback="true" pages="0,6-7,32,46,253-254"> - <font weight="400" style="normal">NotoSansNKo-Regular.ttf</font> + <font font_name="Noto Sans NKo" postscript_name="NotoSansNKo" style="normal" weight="400">NotoSansNKo-Regular.ttf</font> </family> <family fallback="true" pages="0,28,254"> - <font weight="400" style="normal">NotoSansOlChiki-Regular.ttf</font> + <font font_name="Noto Sans Ol Chiki" postscript_name="NotoSansOlChiki" style="normal" weight="400">NotoSansOlChiki-Regular.ttf</font> </family> <family fallback="true" pages="0,169,254"> - <font weight="400" style="normal">NotoSansRejang-Regular.ttf</font> + <font font_name="Noto Sans Rejang" postscript_name="NotoSansRejang" style="normal" weight="400">NotoSansRejang-Regular.ttf</font> </family> <family fallback="true" pages="0,32,37,168,254"> - <font weight="400" style="normal">NotoSansSaurashtra-Regular.ttf</font> + <font font_name="Noto Sans Saurashtra" postscript_name="NotoSansSaurashtra" style="normal" weight="400">NotoSansSaurashtra-Regular.ttf</font> </family> <family fallback="true" pages="0,27-28,254"> - <font weight="400" style="normal">NotoSansSundanese-Regular.ttf</font> + <font font_name="Noto Sans Sundanese" postscript_name="NotoSansSundanese" style="normal" weight="400">NotoSansSundanese-Regular.ttf</font> </family> <family fallback="true" pages="0,9,32,37,168,254"> - <font weight="400" style="normal">NotoSansSylotiNagri-Regular.ttf</font> + <font font_name="Noto Sans Syloti Nagri" postscript_name="NotoSansSylotiNagri" style="normal" weight="400">NotoSansSylotiNagri-Regular.ttf</font> </family> <family fallback="true" pages="0,3,6-7,32,34,37-38,254"> - <font weight="400" style="normal">NotoSansSyriacEstrangela-Regular.ttf</font> + <font font_name="Noto Sans Syriac Estrangela" postscript_name="NotoSansSyriacEstrangela" style="normal" weight="400">NotoSansSyriacEstrangela-Regular.ttf</font> </family> <family fallback="true" pages="0,23,254"> - <font weight="400" style="normal">NotoSansTagbanwa-Regular.ttf</font> + <font font_name="Noto Sans Tagbanwa" postscript_name="NotoSansTagbanwa" style="normal" weight="400">NotoSansTagbanwa-Regular.ttf</font> </family> <family fallback="true" pages="0,26,32,34,37,254"> - <font weight="400" style="normal">NotoSansTaiTham-Regular.ttf</font> + <font font_name="Noto Sans Tai Tham" postscript_name="NotoSansTaiTham" style="normal" weight="400">NotoSansTaiTham-Regular.ttf</font> </family> <family fallback="true" pages="0,32,37,167,170,254"> - <font weight="400" style="normal">NotoSansTaiViet-Regular.ttf</font> + <font font_name="Noto Sans Tai Viet" postscript_name="NotoSansTaiViet" style="normal" weight="400">NotoSansTaiViet-Regular.ttf</font> </family> <family fallback="true" pages="0,15,32,37,254"> - <font weight="400" style="normal">NotoSansTibetan-Regular.ttf</font> + <font font_name="Noto Sans Tibetan" postscript_name="NotoSansTibetan" style="normal" weight="400">NotoSansTibetan-Regular.ttf</font> </family> <family fallback="true" pages="0,3,32,45,254"> - <font weight="400" style="normal">NotoSansTifinagh-Regular.ttf</font> + <font font_name="Noto Sans Tifinagh" postscript_name="NotoSansTifinagh" style="normal" weight="400">NotoSansTifinagh-Regular.ttf</font> </family> <family fallback="true" pages="0,165-166,254"> - <font weight="400" style="normal">NotoSansVai-Regular.ttf</font> + <font font_name="Noto Sans Vai" postscript_name="NotoSansVai" style="normal" weight="400">NotoSansVai-Regular.ttf</font> </family> <family fallback="true" pages="0,160-164,254"> - <font weight="400" style="normal">NotoSansYi-Regular.ttf</font> + <font font_name="Noto Sans Yi" postscript_name="NotoSansYi" style="normal" weight="400">NotoSansYi-Regular.ttf</font> </family> <family fallback="true" pages="32-43"> - <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> + <font font_name="Noto Sans Symbols" postscript_name="NotoSansSymbols" style="normal" weight="400">NotoSansSymbols-Regular-Subsetted.ttf</font> </family> <family fallback="true" lang="zh-Hans" pages="0,2,32-39,41,43,46-159,249-250,254-255,497-498,512-513,518,524,531-533,553,565,572,574,577,584-586,597,602,606,610,612,614-615,617,619-620,632,639,644,646-647,651-652,654,662,664,671,679-682,687,689,691-696,698-702,704-718"> - <font weight="400" style="normal">NotoSansSC-Regular.otf</font> + <font font_name="Noto Sans SC Regular" postscript_name="NotoSansSC-Regular" style="normal" weight="400">NotoSansSC-Regular.otf</font> </family> <family fallback="true" lang="zh-Hant" pages="0,32,34,46-48,50,52-159,249-250,254-255,498,512-657,660-661,663-678,685,760-761"> - <font weight="400" style="normal">NotoSansTC-Regular.otf</font> + <font font_name="Noto Sans TC Regular" postscript_name="NotoSansTC-Regular" style="normal" weight="400">NotoSansTC-Regular.otf</font> </family> <family fallback="true" lang="ja" pages="0,32,34-35,46-159,249-250,254-255,498,512-523,525-527,530-538,540-543,545-547,550,552,554-559,561,563-568,570,572-573,575-579,582-584,586-594,596-608,610-612,614-618,620,622-625,627-628,630-631,633-638,640,642-646,649-655,658,660-664,666,669-678,681,695-696,760-761"> - <font weight="400" style="normal">NotoSansJP-Regular.otf</font> + <font font_name="Noto Sans JP Regular" postscript_name="NotoSansJP-Regular" style="normal" weight="400">NotoSansJP-Regular.otf</font> </family> <family fallback="true" lang="ko" pages="0,17,32,48-50,169,172-215,255"> - <font weight="400" style="normal">NotoSansKR-Regular.otf</font> + <font font_name="Noto Sans KR Regular" postscript_name="NotoSansKR-Regular" style="normal" weight="400">NotoSansKR-Regular.otf</font> </family> <family fallback="true" pages="0,17,32,49-50,172-215"> - <font weight="400" style="normal">NanumGothic.ttf</font> + <font font_name="NanumGothic" postscript_name="NanumGothic" style="normal" weight="400">NanumGothic.ttf</font> </family> <family fallback="true" pages="0,32-33,35-39,41,43,48,50,224,254-255,496-502,4068"> - <font weight="400" style="normal">NotoEmoji-Regular.ttf</font> + <font font_name="Noto Emoji" postscript_name="NotoEmoji" style="normal" weight="400">NotoEmoji-Regular.ttf</font> </family> <family fallback="true" pages="0,14,17,32,48-51,77-159,172-215,249-250,254-255,260"> - <font weight="400" style="normal">DroidSansFallback.ttf</font> + <font font_name="Droid Sans Fallback" postscript_name="DroidSansFallback" style="normal" weight="400">DroidSansFallback.ttf</font> </family> <family fallback="true" lang="ja" pages="0,2-4,32-38,48,50-51,78-159,249-250,255"> - <font weight="400" style="normal">MTLmr3m.ttf</font> + <font font_name="MotoyaLMaru W3 mono" postscript_name="MotoyaLMaru-W3-90ms-RKSJ-H" style="normal" weight="400">MTLmr3m.ttf</font> </family> <!-- Tai Le and Mongolian are intentionally kept last, to make sure they don't override the East Asian punctuation for Chinese. --> <family fallback="true" pages="0,16,25,48,254"> - <font weight="400" style="normal">NotoSansTaiLe-Regular.ttf</font> + <font font_name="Noto Sans Tai Le" postscript_name="NotoSansTaiLe" style="normal" weight="400">NotoSansTaiLe-Regular.ttf</font> </family> <family fallback="true" pages="0,24,32,36-37,48,254"> - <font weight="400" style="normal">NotoSansMongolian-Regular.ttf</font> + <font font_name="Noto Sans Mongolian" postscript_name="NotoSansMongolian" style="normal" weight="400">NotoSansMongolian-Regular.ttf</font> </family> </familyset>
diff --git a/src/cobalt/content/ssl/certs/0a775a30.0 b/src/cobalt/content/ssl/certs/0a775a30.0 new file mode 100644 index 0000000..7d540a4 --- /dev/null +++ b/src/cobalt/content/ssl/certs/0a775a30.0
@@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout +736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A +DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk +fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA +njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd +-----END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/1001acf7.0 b/src/cobalt/content/ssl/certs/1001acf7.0 new file mode 100644 index 0000000..5496703 --- /dev/null +++ b/src/cobalt/content/ssl/certs/1001acf7.0
@@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy +MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl +cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM +f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX +mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 +zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P +fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc +vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 +Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp +zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO +Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW +k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ +DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF +lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW +Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 +d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z +XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR +gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 +d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv +J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg +DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM ++SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy +F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 +SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws +E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl +-----END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/626dceaf.0 b/src/cobalt/content/ssl/certs/626dceaf.0 new file mode 100644 index 0000000..984f1d1 --- /dev/null +++ b/src/cobalt/content/ssl/certs/626dceaf.0
@@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy +MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl +cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv +CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg +GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu +XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd +re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu +PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1 +mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K +8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj +x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR +nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0 +kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok +twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV +HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp +8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT +vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT +z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA +pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb +pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB +R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R +RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk +0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC +5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF +izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn +yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC +-----END CERTIFICATE-----
diff --git a/src/cobalt/content/ssl/certs/a3418fda.0 b/src/cobalt/content/ssl/certs/a3418fda.0 new file mode 100644 index 0000000..07372d3 --- /dev/null +++ b/src/cobalt/content/ssl/certs/a3418fda.0
@@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw +CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU +MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw +MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp +Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA +IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu +hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l +xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0 +CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx +sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w== +-----END CERTIFICATE-----
diff --git a/src/cobalt/css_parser/css_parser.gyp b/src/cobalt/css_parser/css_parser.gyp index 185ee0b..32f9c0d 100644 --- a/src/cobalt/css_parser/css_parser.gyp +++ b/src/cobalt/css_parser/css_parser.gyp
@@ -111,6 +111,7 @@ 'dependencies': [ '<(DEPTH)/cobalt/base/base.gyp:base', '<(DEPTH)/cobalt/cssom/cssom.gyp:cssom', + '<(DEPTH)/nb/nb.gyp:nb', '<(DEPTH)/third_party/icu/icu.gyp:icuuc', 'css_grammar', ],
diff --git a/src/cobalt/css_parser/grammar.h b/src/cobalt/css_parser/grammar.h index 7168cb1..2e21c88 100644 --- a/src/cobalt/css_parser/grammar.h +++ b/src/cobalt/css_parser/grammar.h
@@ -55,6 +55,8 @@ #include "cobalt/cssom/css_style_declaration.h" #include "cobalt/cssom/css_style_rule.h" #include "cobalt/cssom/css_style_sheet.h" +#include "cobalt/cssom/filter_function.h" +#include "cobalt/cssom/filter_function_list_value.h" #include "cobalt/cssom/integer_value.h" #include "cobalt/cssom/keyword_value.h" #include "cobalt/cssom/length_value.h" @@ -64,6 +66,7 @@ #include "cobalt/cssom/media_feature_keyword_value.h" #include "cobalt/cssom/media_list.h" #include "cobalt/cssom/media_query.h" +#include "cobalt/cssom/mtm_function.h" #include "cobalt/cssom/number_value.h" #include "cobalt/cssom/percentage_value.h" #include "cobalt/cssom/property_key_list_value.h" @@ -78,6 +81,8 @@ #include "cobalt/cssom/transform_function.h" #include "cobalt/cssom/transform_function_list_value.h" #include "cobalt/cssom/url_src_value.h" +#include "third_party/glm/glm/gtc/type_ptr.hpp" +#include "third_party/glm/glm/mat4x4.hpp" namespace cobalt { namespace css_parser { @@ -100,7 +105,7 @@ #if defined(OS_STARBOARD) #include "starboard/memory.h" -#define YYFREE SbMemoryFree +#define YYFREE SbMemoryDeallocate #define YYMALLOC SbMemoryAllocate #endif
diff --git a/src/cobalt/css_parser/grammar.y b/src/cobalt/css_parser/grammar.y index 34a7143..e4bb9a1 100644 --- a/src/cobalt/css_parser/grammar.y +++ b/src/cobalt/css_parser/grammar.y
@@ -114,6 +114,7 @@ %token kColorToken // color %token kContentToken // content %token kDisplayToken // display +%token kFilterToken // filter %token kFontToken // font %token kFontFamilyToken // font-family %token kFontSizeToken // font-size @@ -220,6 +221,7 @@ // %token kLeftToken // left - also property name token %token kMaroonToken // maroon %token kMiddleToken // middle +%token kMonoscopicToken // monoscopic %token kMonospaceToken // monospace %token kNavyToken // navy %token kNoneToken // none @@ -247,6 +249,8 @@ %token kStaticToken // static %token kStepEndToken // step-end %token kStepStartToken // step-start +%token kStereoscopicLeftRightToken // stereoscopic-left-right +%token kStereoscopicTopBottomToken // stereoscopic-top-bottom %token kTealToken // teal %token kToToken // to // %token kTopToken // top - also property name token @@ -353,6 +357,7 @@ %token kLinearGradientFunctionToken // linear-gradient( %token kLocalFunctionToken // local( %token kMatrixFunctionToken // matrix( +%token kMatrix3dFunctionToken // matrix3d( %token kNotFunctionToken // not( %token kNthChildFunctionToken // nth-child( %token kNthLastChildFunctionToken // nth-last-child( @@ -370,6 +375,7 @@ %token kRadialGradientFunctionToken // radial-gradient( %token kRGBFunctionToken // rgb( %token kRGBAFunctionToken // rgba( +%token kCobaltMtmFunctionToken // -cobalt-mtm( // Tokens with a string value. %token <string> kStringToken // "...", '...' @@ -582,8 +588,17 @@ white_space_property_value width_property_value z_index_property_value + filter_property_value %destructor { SafeRelease($$); } <property_value> +%union { std::vector<float>* number_matrix; } +%type <number_matrix> number_matrix +%destructor { delete $$; } <number_matrix> + +%union { glm::mat4* matrix4x4; } +%type <matrix4x4> cobalt_mtm_transform_function +%destructor { delete $$; } <matrix4x4> + %union { MarginOrPaddingShorthand* margin_or_padding_shorthand; } %type <margin_or_padding_shorthand> margin_property_value padding_property_value %destructor { delete $$; } <margin_or_padding_shorthand> @@ -763,6 +778,29 @@ %type <transform_functions> transform_list %destructor { delete $$; } <transform_functions> +%union { cssom::FilterFunction* filter_function; } +%type <filter_function> cobalt_mtm_filter_function +%type <filter_function> filter_function +%destructor { delete $$; } <filter_function> + +%union { cssom::FilterFunctionListValue::Builder* cobalt_mtm_filter_functions; } +%type <cobalt_mtm_filter_functions> filter_function_list +%destructor { delete $$; } <cobalt_mtm_filter_functions> + +%union { + cssom::MTMFunction::ResolutionMatchedMeshListBuilder* cobalt_mtm_resolution_matched_meshes; } +%type <cobalt_mtm_resolution_matched_meshes> cobalt_mtm_resolution_matched_mesh_list +%destructor { delete $$; } <cobalt_mtm_resolution_matched_meshes> + +%union { cssom::MTMFunction::ResolutionMatchedMesh* cobalt_mtm_resolution_matched_mesh; } +%type <cobalt_mtm_resolution_matched_mesh> cobalt_mtm_resolution_matched_mesh +%destructor { delete $$; } <cobalt_mtm_resolution_matched_mesh> + +%union { cssom::KeywordValue* stereo_mode; } +%type <stereo_mode> maybe_cobalt_mtm_stereo_mode; +%type <stereo_mode> cobalt_mtm_stereo_mode; +%destructor { SafeRelease($$); } <stereo_mode> + %union { cssom::TimeListValue::Builder* time_list; } %type <time_list> comma_separated_time_list %destructor { delete $$; } <time_list> @@ -1371,6 +1409,10 @@ $$ = TrivialStringPiece::FromCString( cssom::GetPropertyName(cssom::kDisplayProperty)); } + | kFilterToken { + $$ = TrivialStringPiece::FromCString( + cssom::GetPropertyName(cssom::kFilterProperty)); + } | kFontToken { $$ = TrivialStringPiece::FromCString( cssom::GetPropertyName(cssom::kFontProperty)); @@ -1718,6 +1760,9 @@ | kMiddleToken { $$ = TrivialStringPiece::FromCString(cssom::kMiddleKeywordName); } + | kMonoscopicToken { + $$ = TrivialStringPiece::FromCString(cssom::kMonoscopicKeywordName); + } | kMonospaceToken { $$ = TrivialStringPiece::FromCString(cssom::kMonospaceKeywordName); } @@ -1796,6 +1841,14 @@ | kStepStartToken { $$ = TrivialStringPiece::FromCString(cssom::kStepStartKeywordName); } + | kStereoscopicLeftRightToken { + $$ = TrivialStringPiece::FromCString( + cssom::kStereoscopicLeftRightKeywordName); + } + | kStereoscopicTopBottomToken { + $$ = TrivialStringPiece::FromCString( + cssom::kStereoscopicTopBottomKeywordName); + } | kTealToken { $$ = TrivialStringPiece::FromCString(cssom::kTealKeywordName); } @@ -3384,7 +3437,7 @@ validated_box_shadow_list: box_shadow_list { scoped_ptr<ShadowPropertyInfo> shadow_property_info($1); - if (!shadow_property_info->IsShadowPropertyValid(ShadowType::kBoxShadow)) { + if (!shadow_property_info->IsShadowPropertyValid(kBoxShadow)) { parser_impl->LogWarning(@1, "invalid box shadow property."); $$ = NULL; } else { @@ -3397,15 +3450,17 @@ comma_separated_box_shadow_list: validated_box_shadow_list { - if ($1) { + scoped_refptr<cssom::PropertyValue> shadow = MakeScopedRefPtrAndRelease($1); + if (shadow) { $$ = new cssom::PropertyListValue::Builder(); - $$->push_back(MakeScopedRefPtrAndRelease($1)); + $$->push_back(shadow); } } | comma_separated_box_shadow_list comma validated_box_shadow_list { $$ = $1; - if ($$ && $3) { - $$->push_back(MakeScopedRefPtrAndRelease($3)); + scoped_refptr<cssom::PropertyValue> shadow = MakeScopedRefPtrAndRelease($3); + if ($$ && shadow) { + $$->push_back(shadow); } } ; @@ -4200,7 +4255,7 @@ validated_text_shadow_list: text_shadow_list { scoped_ptr<ShadowPropertyInfo> shadow_property_info($1); - if (!shadow_property_info->IsShadowPropertyValid(ShadowType::kTextShadow)) { + if (!shadow_property_info->IsShadowPropertyValid(kTextShadow)) { parser_impl->LogWarning(@1, "invalid text shadow property."); $$ = NULL; } else { @@ -5761,6 +5816,12 @@ MakeScopedRefPtrAndRelease($4), $5) : NULL; } + | kFilterToken maybe_whitespace colon filter_property_value + maybe_important { + $$ = $4 ? new PropertyDeclaration(cssom::kFilterProperty, + MakeScopedRefPtrAndRelease($4), $5) + : NULL; + } | kFontToken maybe_whitespace colon font_property_value maybe_important { scoped_ptr<FontShorthand> font($4); DCHECK(font); @@ -6406,3 +6467,122 @@ } } ; + +// Filters that can be applied to the object's rendering. +// https://www.w3.org/TR/filter-effects-1/#FilterProperty +filter_property_value: + kNoneToken maybe_whitespace { + $$ = AddRef(cssom::KeywordValue::GetNone().get()); + } + | filter_function_list { + scoped_ptr<cssom::FilterFunctionListValue::Builder> property_value($1); + $$ = AddRef(new cssom::FilterFunctionListValue(property_value->Pass())); + } + | common_values + ; + +filter_function_list: + // TODO: Parse list of filter_function's. This only parses one-element lists. + filter_function { + $$ = new cssom::FilterFunctionListValue::Builder(); + $$->push_back($1); + } + ; + +// The set of allowed filter functions. +// https://www.w3.org/TR/filter-effects-1/ +filter_function: + cobalt_mtm_filter_function { + $$ = $1; + } + ; + +cobalt_mtm_filter_function: + // Encodes an mtm filter. Currently the only type of filter function supported. + kCobaltMtmFunctionToken maybe_whitespace url + cobalt_mtm_resolution_matched_mesh_list comma angle angle comma + cobalt_mtm_transform_function maybe_cobalt_mtm_stereo_mode + ')' maybe_whitespace { + scoped_ptr<cssom::MTMFunction::ResolutionMatchedMeshListBuilder> + resolution_matched_mesh_urls($4); + scoped_ptr<glm::mat4> transform($9); + + $$ = new cssom::MTMFunction( + MakeScopedRefPtrAndRelease($3), + resolution_matched_mesh_urls->Pass(), + $6, + $7, + *transform, + MakeScopedRefPtrAndRelease($10)); + } + ; + +cobalt_mtm_resolution_matched_mesh_list: + /* empty */ { + $$ = new cssom::MTMFunction::ResolutionMatchedMeshListBuilder(); + } + // Specifies a different mesh for a particular image resolution. + | cobalt_mtm_resolution_matched_mesh_list cobalt_mtm_resolution_matched_mesh { + $$ = $1; + $$->push_back($2); + } + ; + +cobalt_mtm_resolution_matched_mesh: + non_negative_integer non_negative_integer url { + $$ = new cssom::MTMFunction::ResolutionMatchedMesh($1, $2, + MakeScopedRefPtrAndRelease($3)); + } + ; + +// The set of transform functions allowed in MTM filters, currently a separate +// production hierarchy from the main <transform_function> and represented as a +// glm::mat4. +// https://www.w3.org/TR/css-transforms-1/#three-d-transform-functions +cobalt_mtm_transform_function: + // Specifies an arbitrary affine 3D transformation, currently the only + // supported transform in MTM. + kMatrix3dFunctionToken maybe_whitespace number_matrix ')' maybe_whitespace { + scoped_ptr<std::vector<float> > matrix($3); + if (matrix == NULL || matrix->size() != 16) { + parser_impl->LogError( + @3, + "matrix3d function expects 16 floating-point numbers as arguments"); + YYERROR; + } else { + // GLM and the W3 spec both use column-major order. + $$ = new glm::mat4(glm::make_mat4(&(*matrix)[0])); + } + } + ; + +number_matrix: + number { + $$ = new std::vector<float>(1, $1); + } + | number_matrix comma number { + $$ = $1; + $$->push_back($3); + } + ; + +maybe_cobalt_mtm_stereo_mode: + /* empty */ { + $$ = AddRef(cssom::KeywordValue::GetMonoscopic().get()); + } + | comma cobalt_mtm_stereo_mode { + $$ = $2; + } + ; + +cobalt_mtm_stereo_mode: + kMonoscopicToken maybe_whitespace { + $$ = AddRef(cssom::KeywordValue::GetMonoscopic().get()); + } + | kStereoscopicLeftRightToken maybe_whitespace { + $$ = AddRef(cssom::KeywordValue::GetStereoscopicLeftRight().get()); + } + | kStereoscopicTopBottomToken maybe_whitespace { + $$ = AddRef(cssom::KeywordValue::GetStereoscopicTopBottom().get()); + } + ;
diff --git a/src/cobalt/css_parser/parser.cc b/src/cobalt/css_parser/parser.cc index d289e3c..cb4fb84 100644 --- a/src/cobalt/css_parser/parser.cc +++ b/src/cobalt/css_parser/parser.cc
@@ -24,6 +24,7 @@ #include <string> #include "base/bind.h" +#include "base/debug/trace_event.h" #include "base/hash_tables.h" #include "base/lazy_instance.h" #include "base/optional.h" @@ -66,6 +67,7 @@ #include "cobalt/cssom/matrix_function.h" #include "cobalt/cssom/media_list.h" #include "cobalt/cssom/media_query.h" +#include "cobalt/cssom/mtm_function.h" #include "cobalt/cssom/next_sibling_combinator.h" #include "cobalt/cssom/not_pseudo_class.h" #include "cobalt/cssom/number_value.h" @@ -83,6 +85,7 @@ #include "cobalt/cssom/unicode_range_value.h" #include "cobalt/cssom/universal_selector.h" #include "cobalt/cssom/url_value.h" +#include "nb/memory_scope.h" namespace cobalt { namespace css_parser { @@ -252,18 +255,21 @@ into_declaration_data_(NULL) {} scoped_refptr<cssom::CSSStyleSheet> ParserImpl::ParseStyleSheet() { + TRACK_MEMORY_SCOPE("CSS"); scanner_.PrependToken(kStyleSheetEntryPointToken); return Parse() ? style_sheet_ : make_scoped_refptr(new cssom::CSSStyleSheet(css_parser_)); } scoped_refptr<cssom::CSSRule> ParserImpl::ParseRule() { + TRACK_MEMORY_SCOPE("CSS"); scanner_.PrependToken(kRuleEntryPointToken); return Parse() ? rule_ : NULL; } scoped_refptr<cssom::CSSDeclaredStyleData> ParserImpl::ParseStyleDeclarationList() { + TRACK_MEMORY_SCOPE("CSS"); scanner_.PrependToken(kStyleDeclarationListEntryPointToken); return Parse() ? style_declaration_data_ : make_scoped_refptr(new cssom::CSSDeclaredStyleData()); @@ -271,6 +277,7 @@ scoped_refptr<cssom::CSSFontFaceDeclarationData> ParserImpl::ParseFontFaceDeclarationList() { + TRACK_MEMORY_SCOPE("CSS"); scanner_.PrependToken(kFontFaceDeclarationListEntryPointToken); return Parse() ? font_face_declaration_data_ : make_scoped_refptr(new cssom::CSSFontFaceDeclarationData()); @@ -288,6 +295,7 @@ scoped_refptr<cssom::PropertyValue> ParserImpl::ParsePropertyValue( const std::string& property_name) { + TRACK_MEMORY_SCOPE("CSS"); Token property_name_token; bool is_property_name_known = scanner_.DetectPropertyNameToken(property_name, &property_name_token); @@ -310,6 +318,7 @@ void ParserImpl::ParsePropertyIntoDeclarationData( const std::string& property_name, cssom::CSSDeclarationData* declaration_data) { + TRACK_MEMORY_SCOPE("CSS"); Token property_name_token; bool is_property_name_known = scanner_.DetectPropertyNameToken(property_name, &property_name_token); @@ -338,11 +347,13 @@ } scoped_refptr<cssom::MediaList> ParserImpl::ParseMediaList() { + TRACK_MEMORY_SCOPE("CSS"); scanner_.PrependToken(kMediaListEntryPointToken); return Parse() ? media_list_ : make_scoped_refptr(new cssom::MediaList()); } scoped_refptr<cssom::MediaQuery> ParserImpl::ParseMediaQuery() { + TRACK_MEMORY_SCOPE("CSS"); scanner_.PrependToken(kMediaQueryEntryPointToken); return Parse() ? media_query_ : make_scoped_refptr(new cssom::MediaQuery()); } @@ -362,8 +373,10 @@ } bool ParserImpl::Parse() { + TRACK_MEMORY_SCOPE("CSS"); // For more information on error codes // see http://www.gnu.org/software/bison/manual/html_node/Parser-Function.html + TRACE_EVENT0("cobalt::css_parser", "ParseImpl::Parse"); last_syntax_error_location_ = base::nullopt; int error_code(yyparse(this)); switch (error_code) {
diff --git a/src/cobalt/css_parser/parser_test.cc b/src/cobalt/css_parser/parser_test.cc index 0aaf421..2de5da7 100644 --- a/src/cobalt/css_parser/parser_test.cc +++ b/src/cobalt/css_parser/parser_test.cc
@@ -38,6 +38,7 @@ #include "cobalt/cssom/css_style_sheet.h" #include "cobalt/cssom/descendant_combinator.h" #include "cobalt/cssom/empty_pseudo_class.h" +#include "cobalt/cssom/filter_function_list_value.h" #include "cobalt/cssom/focus_pseudo_class.h" #include "cobalt/cssom/following_sibling_combinator.h" #include "cobalt/cssom/font_style_value.h" @@ -52,6 +53,7 @@ #include "cobalt/cssom/matrix_function.h" #include "cobalt/cssom/media_list.h" #include "cobalt/cssom/media_query.h" +#include "cobalt/cssom/mtm_function.h" #include "cobalt/cssom/next_sibling_combinator.h" #include "cobalt/cssom/not_pseudo_class.h" #include "cobalt/cssom/number_value.h" @@ -7973,7 +7975,7 @@ TEST_F(ParserTest, ParsesFontFaceSrcLocalString) { scoped_refptr<cssom::CSSFontFaceDeclarationData> font_face = - parser_.ParseFontFaceDeclarationList("src: local('Roboto');", + parser_.ParseFontFaceDeclarationList("src: local('Roboto Regular');", source_location_); scoped_refptr<cssom::PropertyListValue> src_list = @@ -7985,12 +7987,12 @@ dynamic_cast<cssom::LocalSrcValue*>( src_list->get_item_modulo_size(0).get()); ASSERT_TRUE(local_src); - EXPECT_EQ("Roboto", local_src->value()); + EXPECT_EQ("Roboto Regular", local_src->value()); } TEST_F(ParserTest, ParsesFontFaceSrcLocalIdentifier) { scoped_refptr<cssom::CSSFontFaceDeclarationData> font_face = - parser_.ParseFontFaceDeclarationList("src: local(Roboto);", + parser_.ParseFontFaceDeclarationList("src: local(Roboto Regular);", source_location_); scoped_refptr<cssom::PropertyListValue> src_list = @@ -8002,7 +8004,7 @@ dynamic_cast<cssom::LocalSrcValue*>( src_list->get_item_modulo_size(0).get()); ASSERT_TRUE(local_src); - EXPECT_EQ("Roboto", local_src->value()); + EXPECT_EQ("Roboto Regular", local_src->value()); } TEST_F(ParserTest, ParsesFontFaceSrcUrlWithoutFormat) { @@ -8262,5 +8264,240 @@ EXPECT_TRUE(media_list->EvaluateConditionValue(size)); } +TEST_F(ParserTest, ParsesNoneFilter) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList("filter: none;", source_location_); + + EXPECT_EQ(cssom::KeywordValue::GetNone(), + style->GetPropertyValue(cssom::kFilterProperty)); +} + +TEST_F(ParserTest, ParsesFilterWithKeywordInitial) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList("filter: initial;", source_location_); + + EXPECT_EQ(cssom::KeywordValue::GetInitial(), + style->GetPropertyValue(cssom::kFilterProperty)); +} + +TEST_F(ParserTest, ParsesFilterWithKeywordInherit) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList("filter: inherit;", source_location_); + + EXPECT_EQ(cssom::KeywordValue::GetInherit(), + style->GetPropertyValue(cssom::kFilterProperty)); +} + +TEST_F(ParserTest, ParsesMtmSingleUrlFilter) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList( + "filter: -cobalt-mtm(url(projection.msh)," + " 180deg 1.5rad," + " matrix3d(1, 0, 0, 0," + " 0, 1, 0, 0," + " 0, 0, 1, 0," + " 0, 0, 0, 1));", + source_location_); + scoped_refptr<cssom::FilterFunctionListValue> filter_list = + dynamic_cast<cssom::FilterFunctionListValue*>( + style->GetPropertyValue(cssom::kFilterProperty).get()); + + ASSERT_TRUE(filter_list); + ASSERT_EQ(1, filter_list->value().size()); + + const cssom::MTMFunction* mtm_function = + dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]); + ASSERT_TRUE(mtm_function); + + EXPECT_EQ(static_cast<float>(M_PI), mtm_function->horizontal_fov()); + EXPECT_EQ(1.5f, mtm_function->vertical_fov()); + + EXPECT_EQ(mtm_function->stereo_mode()->value(), + cssom::KeywordValue::Value::kMonoscopic); +} + +TEST_F(ParserTest, ParsesMtmResolutionMatchedUrlsFilter) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList( + "filter: -cobalt-mtm(url(projection.msh)" + " 640 480 url(p2.msh)" + " 1920 1080 url(yeehaw.msh)" + " 33 22 url(yoda.msh)," + " 100deg 60deg," + " matrix3d(1, 0, 0, 5," + " 0, 2, 0, 0," + " 6, 0, 3, 0," + " 0, 7, 0, 4));", + source_location_); + + scoped_refptr<cssom::FilterFunctionListValue> filter_list = + dynamic_cast<cssom::FilterFunctionListValue*>( + style->GetPropertyValue(cssom::kFilterProperty).get()); + + ASSERT_TRUE(filter_list); + const cssom::MTMFunction* mtm_function = + dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]); + + const cssom::MTMFunction::ResolutionMatchedMeshListBuilder& meshes = + mtm_function->resolution_matched_meshes(); + + ASSERT_EQ(3, meshes.size()); + EXPECT_EQ(640, meshes[0]->width_match()); + EXPECT_EQ(22, meshes[2]->height_match()); + + EXPECT_EQ( + "yeehaw.msh", + dynamic_cast<cssom::URLValue*>(meshes[1]->mesh_url().get())->value()); + + EXPECT_EQ(mtm_function->stereo_mode()->value(), + cssom::KeywordValue::Value::kMonoscopic); +} + +TEST_F(ParserTest, ParsesMtmTransformMatrixFilter) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList( + "filter: -cobalt-mtm(url(p.msh)," + " 100deg 60deg," + " matrix3d(1, 0, 0, 5," + " 0, 2, 0, 0," + " 6, 0, 3, 0," + " 0, 7, 0, 4));", + source_location_); + + scoped_refptr<cssom::FilterFunctionListValue> filter_list = + dynamic_cast<cssom::FilterFunctionListValue*>( + style->GetPropertyValue(cssom::kFilterProperty).get()); + + ASSERT_TRUE(filter_list); + const cssom::MTMFunction* mtm_function = + dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]); + + const glm::mat4& actual = mtm_function->transform(); + EXPECT_TRUE( + glm::all(glm::equal(glm::vec4(1.0f, 0.0f, 0.0f, 5.0f), actual[0]))); + EXPECT_TRUE( + glm::all(glm::equal(glm::vec4(6.0f, 0.0f, 3.0f, 0.0f), actual[2]))); + EXPECT_EQ(7.0f, actual[3][1]); + EXPECT_EQ(2.0f, actual[1][1]); + EXPECT_EQ(0.0f, actual[2][3]); + EXPECT_EQ(4.0f, actual[3][3]); + + EXPECT_EQ(mtm_function->stereo_mode()->value(), + cssom::KeywordValue::Value::kMonoscopic); +} + +TEST_F(ParserTest, ParsesMtmMonoscopicStereoModeFilter) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList( + "filter: -cobalt-mtm(url(p.msh)," + " 100deg 60deg," + " matrix3d(1, 0, 0, 5," + " 0, 2, 0, 0," + " 6, 0, 3, 0," + " 0, 7, 0, 4)," + " monoscopic);", + source_location_); + scoped_refptr<cssom::FilterFunctionListValue> filter_list = + dynamic_cast<cssom::FilterFunctionListValue*>( + style->GetPropertyValue(cssom::kFilterProperty).get()); + + ASSERT_TRUE(filter_list); + ASSERT_EQ(1, filter_list->value().size()); + + const cssom::MTMFunction* mtm_function = + dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]); + ASSERT_TRUE(mtm_function); + + EXPECT_EQ(mtm_function->stereo_mode()->value(), + cssom::KeywordValue::Value::kMonoscopic); +} + +TEST_F(ParserTest, ParsesMtmStereoscopicLeftRightStereoModeFilter) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList( + "filter: -cobalt-mtm(url(p.msh)," + " 100deg 60deg," + " matrix3d(1, 0, 0, 5," + " 0, 2, 0, 0," + " 6, 0, 3, 0," + " 0, 7, 0, 4)," + " stereoscopic-left-right);", + source_location_); + scoped_refptr<cssom::FilterFunctionListValue> filter_list = + dynamic_cast<cssom::FilterFunctionListValue*>( + style->GetPropertyValue(cssom::kFilterProperty).get()); + + ASSERT_TRUE(filter_list); + ASSERT_EQ(1, filter_list->value().size()); + + const cssom::MTMFunction* mtm_function = + dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]); + ASSERT_TRUE(mtm_function); + + EXPECT_EQ(mtm_function->stereo_mode()->value(), + cssom::KeywordValue::Value::kStereoscopicLeftRight); +} + +TEST_F(ParserTest, ParsesMtmStereoscopicTopBottomStereoModeFilter) { + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList( + "filter: -cobalt-mtm(url(p.msh)," + " 100deg 60deg," + " matrix3d(1, 0, 0, 5," + " 0, 2, 0, 0," + " 6, 0, 3, 0," + " 0, 7, 0, 4)," + " stereoscopic-top-bottom);", + source_location_); + scoped_refptr<cssom::FilterFunctionListValue> filter_list = + dynamic_cast<cssom::FilterFunctionListValue*>( + style->GetPropertyValue(cssom::kFilterProperty).get()); + + ASSERT_TRUE(filter_list); + ASSERT_EQ(1, filter_list->value().size()); + + const cssom::MTMFunction* mtm_function = + dynamic_cast<const cssom::MTMFunction*>(filter_list->value()[0]); + ASSERT_TRUE(mtm_function); + + EXPECT_EQ(mtm_function->stereo_mode()->value(), + cssom::KeywordValue::Value::kStereoscopicTopBottom); +} + +TEST_F(ParserTest, HandlesInvalidMtmStereoMode) { + EXPECT_CALL(parser_observer_, + OnWarning("[object ParserTest]:1:9: warning: unsupported value")); + + scoped_refptr<cssom::CSSDeclaredStyleData> style = + parser_.ParseStyleDeclarationList( + "filter: -cobalt-mtm(url(p.msh)," + " 100deg 60deg," + " matrix3d(1, 0, 0, 5," + " 0, 2, 0, 0," + " 6, 0, 3, 0," + " 0, 7, 0, 4)," + " invalid-stereo-mode);", + source_location_); + scoped_refptr<cssom::FilterFunctionListValue> filter_list = + dynamic_cast<cssom::FilterFunctionListValue*>( + style->GetPropertyValue(cssom::kFilterProperty).get()); + + ASSERT_FALSE(filter_list); +} + +TEST_F(ParserTest, EmptyPropertyValueRemovesProperty) { + // Test that parsing an empty property value removes the previously declared + // property value. + scoped_refptr<cssom::CSSDeclaredStyleData> style_data = + parser_.ParseStyleDeclarationList("display: inline;", source_location_); + + scoped_refptr<cssom::CSSDeclaredStyleDeclaration> style = + new cssom::CSSDeclaredStyleDeclaration(style_data, &parser_); + + style->SetPropertyValue(std::string("display"), std::string(), NULL); + EXPECT_EQ(style->GetPropertyValue("display"), ""); + EXPECT_FALSE(style_data->GetPropertyValue(cssom::kDisplayProperty)); +} + } // namespace css_parser } // namespace cobalt
diff --git a/src/cobalt/css_parser/position_parse_structures.cc b/src/cobalt/css_parser/position_parse_structures.cc index a3f3422..50c13f3 100644 --- a/src/cobalt/css_parser/position_parse_structures.cc +++ b/src/cobalt/css_parser/position_parse_structures.cc
@@ -119,7 +119,7 @@ break; } default: - NOTREACHED(); + // Position value couldn't take more than 4 elements. return false; } break;
diff --git a/src/cobalt/css_parser/scanner.cc b/src/cobalt/css_parser/scanner.cc index fadfebb..a19db4d 100644 --- a/src/cobalt/css_parser/scanner.cc +++ b/src/cobalt/css_parser/scanner.cc
@@ -312,7 +312,8 @@ // The input must be part of an identifier if "actual" or "expected" // contains '-'. Otherwise ToAsciiLowerUnchecked('\r') would be equal // to '-'. - DCHECK((*expected >= 'a' && *expected <= 'z') || *expected == '-'); + DCHECK((*expected >= 'a' && *expected <= 'z') || + (*expected >= '0' && *expected <= '9') || *expected == '-'); DCHECK(*expected != '-' || IsCssLetter(*actual)); if (ToAsciiLowerUnchecked(*actual++) != (*expected++)) { return false; @@ -772,6 +773,15 @@ // Cache the open brace. open_braces_.push('('); + if (!has_escape) { + Token function_token; + if (DetectKnownFunctionTokenAndMaybeChangeParsingMode( + name, &function_token)) { + ++input_iterator_; + return function_token; + } + } + ++input_iterator_; token_value->string = name; return kInvalidFunctionToken; @@ -1326,6 +1336,11 @@ return true; } if (IsEqualToCssIdentifier( + name.begin, cssom::GetPropertyName(cssom::kFilterProperty))) { + *property_name_token = kFilterToken; + return true; + } + if (IsEqualToCssIdentifier( name.begin, cssom::GetPropertyName(cssom::kHeightProperty))) { *property_name_token = kHeightToken; return true; @@ -2189,6 +2204,10 @@ *property_value_token = kStepStartToken; return true; } + if (IsEqualToCssIdentifier(name.begin, cssom::kMonoscopicKeywordName)) { + *property_value_token = kMonoscopicToken; + return true; + } return false; case 11: @@ -2247,6 +2266,19 @@ return true; } return false; + + case 23: + if (IsEqualToCssIdentifier(name.begin, + cssom::kStereoscopicLeftRightKeywordName)) { + *property_value_token = kStereoscopicLeftRightToken; + return true; + } + if (IsEqualToCssIdentifier(name.begin, + cssom::kStereoscopicTopBottomKeywordName)) { + *property_value_token = kStereoscopicTopBottomToken; + return true; + } + return false; } return false; @@ -2417,6 +2449,13 @@ } return false; + case 8: + if (IsEqualToCssIdentifier(name.begin, "matrix3d")) { + *known_function_token = kMatrix3dFunctionToken; + return true; + } + return false; + case 9: if (IsEqualToCssIdentifier(name.begin, "translate")) { *known_function_token = kTranslateFunctionToken; @@ -2450,6 +2489,10 @@ *known_function_token = kNthOfTypeFunctionToken; return true; } + if (IsEqualToCssIdentifier(name.begin, "-cobalt-mtm")) { + *known_function_token = kCobaltMtmFunctionToken; + return true; + } return false; case 12:
diff --git a/src/cobalt/css_parser/scanner_test.cc b/src/cobalt/css_parser/scanner_test.cc index c1afabf..16b24f7 100644 --- a/src/cobalt/css_parser/scanner_test.cc +++ b/src/cobalt/css_parser/scanner_test.cc
@@ -172,6 +172,17 @@ ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); } +TEST_F(ScannerTest, ScansInvalidFunctionWithNumber) { + Scanner scanner("sample-matrix4d()", &string_pool_); + + ASSERT_EQ(kInvalidFunctionToken, + yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ("sample-matrix4d", token_value_.string); + + ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); +} + TEST_F(ScannerTest, ScansFunctionLikeMediaAnd) { Scanner scanner("@media tv and(monochrome)", &string_pool_); @@ -269,6 +280,15 @@ ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); } +TEST_F(ScannerTest, ScansIdentifierWithNumber) { + Scanner scanner("matrix3d5s7wss-47", &string_pool_); + + ASSERT_EQ(kIdentifierToken, yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ("matrix3d5s7wss-47", token_value_.string); + + ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); +} + TEST_F(ScannerTest, ScansDot) { Scanner scanner(".", &string_pool_); @@ -414,6 +434,17 @@ ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); } +TEST_F(ScannerTest, ScansUnknownDashFunctionWithNumber) { + Scanner scanner("-cobalt-ma5555gic()", &string_pool_); + + ASSERT_EQ(kInvalidFunctionToken, + yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ("-cobalt-ma5555gic", token_value_.string); + + ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); +} + TEST_F(ScannerTest, ScansUnknownDashFunctionWithoutClosingAtEnd) { Scanner scanner("-cobalt-magic(", &string_pool_); @@ -425,6 +456,26 @@ ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); } +TEST_F(ScannerTest, ScansKnownDashFunction) { + Scanner scanner("-cobalt-mtm()", &string_pool_); + + ASSERT_EQ(kCobaltMtmFunctionToken, + yylex(&token_value_, &token_location_, &scanner)); + + ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); +} + +TEST_F(ScannerTest, ScansKnownDashFunctionWithoutClosingAtEnd) { + Scanner scanner("-cobalt-mtm(", &string_pool_); + + ASSERT_EQ(kCobaltMtmFunctionToken, + yylex(&token_value_, &token_location_, &scanner)); + + ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); +} + TEST_F(ScannerTest, ScansMinusNPlusConstant) { Scanner scanner("nth-child(-n+2)", &string_pool_); @@ -943,6 +994,15 @@ ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); } +TEST_F(ScannerTest, ScansMatrix3dFunction) { + Scanner scanner("matrix3d()", &string_pool_); + + ASSERT_EQ(kMatrix3dFunctionToken, + yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ(')', yylex(&token_value_, &token_location_, &scanner)); + ASSERT_EQ(kEndOfFileToken, yylex(&token_value_, &token_location_, &scanner)); +} + TEST_F(ScannerTest, ScansNthOfTypeFunction) { Scanner scanner("nth-of-type()", &string_pool_);
diff --git a/src/cobalt/cssom/CSSStyleDeclaration.idl b/src/cobalt/cssom/CSSStyleDeclaration.idl index fc13d7a..12793bf 100644 --- a/src/cobalt/cssom/CSSStyleDeclaration.idl +++ b/src/cobalt/cssom/CSSStyleDeclaration.idl
@@ -111,6 +111,11 @@ readonly attribute unsigned long length; getter DOMString? item(unsigned long index); DOMString getPropertyValue(DOMString property); - [RaisesException] void setPropertyValue(DOMString property, DOMString value); + [RaisesException] void setProperty( + DOMString property, [TreatNullAs=EmptyString] DOMString value, + [TreatNullAs=EmptyString] optional DOMString priority); + [RaisesException] void setPropertyValue( + DOMString property, [TreatNullAs=EmptyString] DOMString value); + [RaisesException] DOMString removeProperty(DOMString property); readonly attribute CSSRule? parentRule; };
diff --git a/src/cobalt/cssom/complex_selector.cc b/src/cobalt/cssom/complex_selector.cc index 5eb9047..b164c77 100644 --- a/src/cobalt/cssom/complex_selector.cc +++ b/src/cobalt/cssom/complex_selector.cc
@@ -16,6 +16,7 @@ #include "cobalt/cssom/complex_selector.h" +#include "base/logging.h" #include "cobalt/cssom/combinator.h" #include "cobalt/cssom/compound_selector.h" #include "cobalt/cssom/selector_visitor.h" @@ -23,6 +24,8 @@ namespace cobalt { namespace cssom { +const int ComplexSelector::kCombinatorLimit = 32; + void ComplexSelector::Accept(SelectorVisitor* visitor) { visitor->VisitComplexSelector(this); } @@ -39,6 +42,18 @@ scoped_ptr<Combinator> combinator, scoped_ptr<CompoundSelector> compound_selector) { DCHECK(first_selector_); + DCHECK(last_selector_); + + if (combinator_count_ >= kCombinatorLimit) { + if (!combinator_limit_exceeded_) { + LOG(WARNING) + << "Maximum number of calls to AppendCombinatorAndSelector exceeded." + " Ignoring additional selectors."; + combinator_limit_exceeded_ = true; + } + return; + } + specificity_.AddFrom(compound_selector->GetSpecificity()); combinator->set_left_selector(last_selector_); @@ -48,6 +63,8 @@ last_selector_->set_right_combinator(combinator.Pass()); last_selector_ = last_selector_->right_combinator()->right_selector(); + + combinator_count_++; } } // namespace cssom
diff --git a/src/cobalt/cssom/complex_selector.h b/src/cobalt/cssom/complex_selector.h index d8d2cae..e20ef8a 100644 --- a/src/cobalt/cssom/complex_selector.h +++ b/src/cobalt/cssom/complex_selector.h
@@ -35,7 +35,12 @@ // https://www.w3.org/TR/selectors4/#complex class ComplexSelector : public Selector { public: - ComplexSelector() : last_selector_(NULL) {} + static const int kCombinatorLimit; + + ComplexSelector() + : last_selector_(NULL), + combinator_count_(0), + combinator_limit_exceeded_(false) {} ~ComplexSelector() OVERRIDE {} // From Selector. @@ -48,6 +53,10 @@ CompoundSelector* first_selector() { return first_selector_.get(); } CompoundSelector* last_selector() { return last_selector_; } + int combinator_count() { + return combinator_count_; + } + // For a chain of compound selectors separated by combinators, AppendSelector // should be first called with the left most compound selector, then // AppendCombinatorAndSelector should be called with each (combinator, @@ -63,6 +72,9 @@ scoped_ptr<CompoundSelector> first_selector_; Specificity specificity_; + int combinator_count_; + bool combinator_limit_exceeded_; + DISALLOW_COPY_AND_ASSIGN(ComplexSelector); };
diff --git a/src/cobalt/cssom/computed_style.cc b/src/cobalt/cssom/computed_style.cc index 9d5d0c9..5fa9201 100644 --- a/src/cobalt/cssom/computed_style.cc +++ b/src/cobalt/cssom/computed_style.cc
@@ -363,6 +363,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNone: case KeywordValue::kNoRepeat: @@ -379,6 +380,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -458,6 +461,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNoRepeat: case KeywordValue::kNone: @@ -475,6 +479,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -561,6 +567,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNone: case KeywordValue::kNoRepeat: @@ -578,6 +585,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -680,6 +689,7 @@ case KeywordValue::kLineThrough: case KeywordValue::kMiddle: case KeywordValue::kMonospace: + case KeywordValue::kMonoscopic: case KeywordValue::kNone: case KeywordValue::kNoRepeat: case KeywordValue::kNormal: @@ -696,6 +706,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -798,6 +810,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNoRepeat: case KeywordValue::kNormal: @@ -814,6 +827,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -916,6 +931,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNone: case KeywordValue::kNoRepeat: @@ -933,6 +949,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -1029,6 +1047,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNone: case KeywordValue::kNoRepeat: @@ -1046,6 +1065,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -1138,6 +1159,7 @@ case KeywordValue::kLineThrough: case KeywordValue::kMiddle: case KeywordValue::kMonospace: + case KeywordValue::kMonoscopic: case KeywordValue::kNoRepeat: case KeywordValue::kNormal: case KeywordValue::kNoWrap: @@ -1153,6 +1175,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -1551,6 +1575,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNoRepeat: case KeywordValue::kNormal: @@ -1567,6 +1592,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -1688,6 +1715,13 @@ absolute_url = url_value->Resolve(base_url_); } + if (!absolute_url.is_valid()) { + DLOG(WARNING) << "Invalid url: " << absolute_url.spec(); + // No further process is needed if the url is invalid. + computed_background_image_ = KeywordValue::GetNone(); + return; + } + computed_background_image_ = new AbsoluteURLValue(absolute_url); } @@ -1817,6 +1851,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNone: case KeywordValue::kNoRepeat: @@ -1834,6 +1869,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -2064,6 +2101,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNormal: case KeywordValue::kNoRepeat: @@ -2080,6 +2118,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -2448,6 +2488,7 @@ case KeywordValue::kLeft: case KeywordValue::kLineThrough: case KeywordValue::kMiddle: + case KeywordValue::kMonoscopic: case KeywordValue::kMonospace: case KeywordValue::kNoRepeat: case KeywordValue::kNormal: @@ -2464,6 +2505,8 @@ case KeywordValue::kSolid: case KeywordValue::kStart: case KeywordValue::kStatic: + case KeywordValue::kStereoscopicLeftRight: + case KeywordValue::kStereoscopicTopBottom: case KeywordValue::kTop: case KeywordValue::kUppercase: case KeywordValue::kVisible: @@ -2912,6 +2955,7 @@ case kBorderTopStyleProperty: case kColorProperty: case kContentProperty: + case kFilterProperty: case kFontFamilyProperty: case kFontStyleProperty: case kOpacityProperty: @@ -3019,6 +3063,7 @@ case kBoxShadowProperty: case kContentProperty: case kDisplayProperty: + case kFilterProperty: case kFontFamilyProperty: case kFontProperty: case kFontStyleProperty:
diff --git a/src/cobalt/cssom/css_computed_style_data.cc b/src/cobalt/cssom/css_computed_style_data.cc index a5e2fcd..509a9b3 100644 --- a/src/cobalt/cssom/css_computed_style_data.cc +++ b/src/cobalt/cssom/css_computed_style_data.cc
@@ -67,16 +67,6 @@ CSSComputedStyleData::~CSSComputedStyleData() {} -unsigned int CSSComputedStyleData::length() const { - // Computed style declarations have all known longhand properties. - return kMaxLonghandPropertyKey + 1; -} - -const char* CSSComputedStyleData::Item(unsigned int index) const { - if (index >= length()) return NULL; - return GetPropertyName(GetLexicographicalLonghandPropertyKey(index)); -} - const scoped_refptr<PropertyValue>& CSSComputedStyleData::GetPropertyValueReference(PropertyKey key) const { DCHECK_GT(key, kNoneProperty); @@ -195,6 +185,7 @@ case kColorProperty: case kContentProperty: case kFontFamilyProperty: + case kFilterProperty: case kFontProperty: case kFontStyleProperty: case kFontWeightProperty:
diff --git a/src/cobalt/cssom/css_computed_style_data.h b/src/cobalt/cssom/css_computed_style_data.h index e303868..fb12f37 100644 --- a/src/cobalt/cssom/css_computed_style_data.h +++ b/src/cobalt/cssom/css_computed_style_data.h
@@ -62,16 +62,6 @@ CSSComputedStyleData(); ~CSSComputedStyleData(); - // The length attribute must return the number of CSS declarations in the - // declarations. - // https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-length - unsigned int length() const; - - // The item(index) method must return the property name of the CSS declaration - // at position index. - // https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-item - const char* Item(unsigned int index) const; - void SetPropertyValue(const PropertyKey key, const scoped_refptr<PropertyValue>& value); @@ -329,6 +319,13 @@ SetPropertyValue(kDisplayProperty, display); } + const scoped_refptr<PropertyValue>& filter() const { + return GetPropertyValueReference(kFilterProperty); + } + void set_filter(const scoped_refptr<PropertyValue>& filter) { + SetPropertyValue(kFilterProperty, filter); + } + const scoped_refptr<PropertyValue>& font_family() const { return GetPropertyValueReference(kFontFamilyProperty); }
diff --git a/src/cobalt/cssom/css_computed_style_data_test.cc b/src/cobalt/cssom/css_computed_style_data_test.cc index 2fcb1ea..4aede0c 100644 --- a/src/cobalt/cssom/css_computed_style_data_test.cc +++ b/src/cobalt/cssom/css_computed_style_data_test.cc
@@ -1191,5 +1191,22 @@ ASSERT_TRUE(style2->DoDeclaredPropertiesMatch(style1)); } +TEST(CSSComputedStyleDataTest, FilterSettersAndGettersAreConsistent) { + scoped_refptr<CSSComputedStyleData> style = new CSSComputedStyleData(); + + EXPECT_EQ(GetPropertyInitialValue(kFilterProperty), style->filter()); + EXPECT_EQ(style->filter(), style->GetPropertyValue(kFilterProperty)); + + style->set_filter(KeywordValue::GetInitial()); + EXPECT_EQ(KeywordValue::GetInitial(), style->filter()); + EXPECT_EQ(KeywordValue::GetInitial(), + style->GetPropertyValue(kFilterProperty)); + + style->SetPropertyValue(kFilterProperty, KeywordValue::GetInherit()); + EXPECT_EQ(KeywordValue::GetInherit(), style->filter()); + EXPECT_EQ(KeywordValue::GetInherit(), + style->GetPropertyValue(kFilterProperty)); +} + } // namespace cssom } // namespace cobalt
diff --git a/src/cobalt/cssom/css_computed_style_declaration.cc b/src/cobalt/cssom/css_computed_style_declaration.cc index ed4b4c8..1ca1566 100644 --- a/src/cobalt/cssom/css_computed_style_declaration.cc +++ b/src/cobalt/cssom/css_computed_style_declaration.cc
@@ -42,7 +42,8 @@ // declarations. // https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-length unsigned int CSSComputedStyleDeclaration::length() const { - return data_ ? data_->length() : 0; + // Computed style declarations have all known longhand properties. + return kMaxLonghandPropertyKey + 1; } // The item(index) method must return the property name of the CSS declaration @@ -50,8 +51,9 @@ // https://www.w3.org/TR/cssom/#dom-cssstyledeclaration-item base::optional<std::string> CSSComputedStyleDeclaration::Item( unsigned int index) const { - const char* item = data_ ? data_->Item(index) : NULL; - return item ? base::optional<std::string>(item) : base::nullopt; + if (index >= length()) return base::nullopt; + return base::optional<std::string>( + GetPropertyName(GetLexicographicalLonghandPropertyKey(index))); } std::string CSSComputedStyleDeclaration::GetDeclaredPropertyValueStringByKey( @@ -72,6 +74,13 @@ exception_state); } +void CSSComputedStyleDeclaration::SetProperty( + const std::string& /*property_name*/, const std::string& /*property_value*/, + const std::string& /*priority*/, script::ExceptionState* exception_state) { + dom::DOMException::Raise(dom::DOMException::kInvalidAccessErr, + exception_state); +} + void CSSComputedStyleDeclaration::SetData( const scoped_refptr<const CSSComputedStyleData>& data) { data_ = data;
diff --git a/src/cobalt/cssom/css_computed_style_declaration.h b/src/cobalt/cssom/css_computed_style_declaration.h index e166979..1735690 100644 --- a/src/cobalt/cssom/css_computed_style_declaration.h +++ b/src/cobalt/cssom/css_computed_style_declaration.h
@@ -38,6 +38,8 @@ // for computed styles. class CSSComputedStyleDeclaration : public CSSStyleDeclaration { public: + using CSSStyleDeclaration::SetProperty; + CSSComputedStyleDeclaration() {} // From CSSStyleDeclaration. @@ -53,6 +55,11 @@ const std::string& property_value, script::ExceptionState* exception_state) OVERRIDE; + void SetProperty(const std::string& property_name, + const std::string& property_value, + const std::string& priority, + script::ExceptionState* exception_state) OVERRIDE; + // Custom. const scoped_refptr<const CSSComputedStyleData>& data() const {
diff --git a/src/cobalt/cssom/css_computed_style_declaration_test.cc b/src/cobalt/cssom/css_computed_style_declaration_test.cc new file mode 100644 index 0000000..d5abd5a --- /dev/null +++ b/src/cobalt/cssom/css_computed_style_declaration_test.cc
@@ -0,0 +1,212 @@ +/* + * Copyright 2016 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/cssom/css_computed_style_data.h" +#include "cobalt/cssom/css_computed_style_declaration.h" +#include "cobalt/cssom/keyword_value.h" +#include "cobalt/cssom/length_value.h" +#include "cobalt/cssom/property_definitions.h" +#include "cobalt/dom/dom_exception.h" +#include "cobalt/script/exception_state.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cobalt { +namespace cssom { + +using ::testing::_; +using ::testing::Return; + +namespace { +class FakeExceptionState : public script::ExceptionState { + public: + void SetException( + const scoped_refptr<script::ScriptException>& exception) OVERRIDE { + dom_exception_ = make_scoped_refptr( + base::polymorphic_downcast<dom::DOMException*>(exception.get())); + } + void SetSimpleException(script::MessageType /*message_type*/, ...) OVERRIDE { + // no-op + } + dom::DOMException::ExceptionCode GetExceptionCode() { + return dom_exception_ ? static_cast<dom::DOMException::ExceptionCode>( + dom_exception_->code()) + : dom::DOMException::kNone; + } + + private: + scoped_refptr<dom::DOMException> dom_exception_; +}; +} // namespace + +TEST(CSSComputedStyleDeclarationTest, CSSTextSetterRaisesException) { + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + FakeExceptionState exception_state; + + const std::string css_text = "font-size: 100px; color: #0047ab;"; + style->set_css_text(css_text, &exception_state); + EXPECT_EQ(dom::DOMException::kInvalidAccessErr, + exception_state.GetExceptionCode()); +} + +TEST(CSSComputedStyleDeclarationTest, DISABLED_CSSTextGetter) { + scoped_refptr<LengthValue> background_size = + new LengthValue(100, kPixelsUnit); + scoped_refptr<LengthValue> bottom = new LengthValue(16, kPixelsUnit); + + scoped_refptr<CSSComputedStyleData> style_data = new CSSComputedStyleData(); + style_data->SetPropertyValue(kBackgroundSizeProperty, background_size); + style_data->SetPropertyValue(kBottomProperty, bottom); + + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + style->SetData(style_data); + EXPECT_EQ(style->css_text(NULL), "background-size: 100px; bottom: 16px;"); +} + +TEST(CSSComputedStyleDeclarationTest, PropertyValueSetterRaisesException) { + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + const std::string background = "rgba(0, 0, 0, .8)"; + FakeExceptionState exception_state; + + style->SetPropertyValue(GetPropertyName(kBackgroundProperty), background, + &exception_state); + EXPECT_EQ(dom::DOMException::kInvalidAccessErr, + exception_state.GetExceptionCode()); +} + +TEST(CSSComputedStyleDeclarationTest, + PropertySetterWithTwoValuesRaisesException) { + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + const std::string background = "rgba(0, 0, 0, .8)"; + FakeExceptionState exception_state; + + style->SetProperty(GetPropertyName(kBackgroundProperty), background, + &exception_state); + EXPECT_EQ(dom::DOMException::kInvalidAccessErr, + exception_state.GetExceptionCode()); +} + +TEST(CSSComputedStyleDeclarationTest, + PropertySetterWithThreeValuesRaisesException) { + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + const std::string background = "rgba(0, 0, 0, .8)"; + FakeExceptionState exception_state; + + style->SetProperty(GetPropertyName(kBackgroundProperty), background, + std::string(), &exception_state); + EXPECT_EQ(dom::DOMException::kInvalidAccessErr, + exception_state.GetExceptionCode()); +} + +TEST(CSSComputedStyleDeclarationTest, RemovePropertyRaisesException) { + scoped_refptr<CSSComputedStyleData> initial_style = + new CSSComputedStyleData(); + initial_style->SetPropertyValue(kDisplayProperty, KeywordValue::GetInline()); + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + style->SetData(initial_style); + + const std::string background = "rgba(0, 0, 0, .8)"; + FakeExceptionState exception_state; + + style->RemoveProperty(GetPropertyName(kDisplayProperty), &exception_state); + EXPECT_EQ(dom::DOMException::kInvalidAccessErr, + exception_state.GetExceptionCode()); +} + +TEST(CSSComputedStyleDeclarationTest, PropertyValueGetter) { + scoped_refptr<CSSComputedStyleData> initial_style = + new CSSComputedStyleData(); + initial_style->SetPropertyValue(kTextAlignProperty, + KeywordValue::GetCenter()); + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + style->SetData(initial_style); + + EXPECT_EQ(style->GetPropertyValue(GetPropertyName(kTextAlignProperty)), + "center"); +} + +TEST(CSSComputedStyleDeclarationTest, + UnknownDeclaredStylePropertyValueShouldBeEmpty) { + scoped_refptr<CSSComputedStyleData> initial_style = + new CSSComputedStyleData(); + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + style->SetData(initial_style); + + EXPECT_EQ(style->GetPropertyValue("cobalt_cobalt_cobalt"), ""); +} + +TEST(CSSComputedStyleDeclarationTest, LengthAttributeGetterEmpty) { + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + + EXPECT_EQ(style->length(), kMaxLonghandPropertyKey + 1); +} + +TEST(CSSComputedStyleDeclarationTest, LengthAttributeGetterNotEmpty) { + scoped_refptr<CSSComputedStyleData> initial_style = + new CSSComputedStyleData(); + initial_style->SetPropertyValue(kDisplayProperty, KeywordValue::GetInline()); + initial_style->SetPropertyValue(kTextAlignProperty, + KeywordValue::GetCenter()); + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + style->SetData(initial_style); + + EXPECT_EQ(style->length(), kMaxLonghandPropertyKey + 1); +} + +TEST(CSSComputedStyleDeclarationTest, ItemGetterEmpty) { + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + + // Computed styles are never actually empty. + EXPECT_TRUE(style->Item(0)); +} + +TEST(CSSComputedStyleDeclarationTest, ItemGetterNotEmpty) { + scoped_refptr<CSSComputedStyleData> initial_style = + new CSSComputedStyleData(); + initial_style->SetPropertyValue(kDisplayProperty, KeywordValue::GetInline()); + initial_style->SetPropertyValue(kTextAlignProperty, + KeywordValue::GetCenter()); + scoped_refptr<CSSComputedStyleDeclaration> style = + new CSSComputedStyleDeclaration(); + style->SetData(initial_style); + + int property_count = 0; + for (unsigned int key = 0; key <= kMaxLonghandPropertyKey; ++key) { + // The order is not important, as long as all properties are represented. + EXPECT_TRUE(style->Item(key)); + if (style->Item(key).value() == GetPropertyName(kDisplayProperty)) { + ++property_count; + } + if (style->Item(key).value() == GetPropertyName(kTextAlignProperty)) { + ++property_count; + } + } + EXPECT_EQ(property_count, 2); +} + +} // namespace cssom +} // namespace cobalt
diff --git a/src/cobalt/cssom/css_declared_style_data.cc b/src/cobalt/cssom/css_declared_style_data.cc index 6768e50..80a05df 100644 --- a/src/cobalt/cssom/css_declared_style_data.cc +++ b/src/cobalt/cssom/css_declared_style_data.cc
@@ -65,7 +65,14 @@ ExpandShorthandProperty(key); for (LonghandPropertySet::const_iterator iter = longhand_properties.begin(); iter != longhand_properties.end(); ++iter) { - ClearPropertyValueAndImportanceForLonghandProperty(*iter); + PropertyKey longhand_key = *iter; + if (IsShorthandProperty(longhand_key)) { + // If the property is another shorthand property, then recurse to clear + // it's registered longhand properties. + ClearPropertyValueAndImportance(longhand_key); + } else { + ClearPropertyValueAndImportanceForLonghandProperty(longhand_key); + } } } else { ClearPropertyValueAndImportanceForLonghandProperty(key);
diff --git a/src/cobalt/cssom/css_declared_style_data_test.cc b/src/cobalt/cssom/css_declared_style_data_test.cc index 31388a8..0761c8d 100644 --- a/src/cobalt/cssom/css_declared_style_data_test.cc +++ b/src/cobalt/cssom/css_declared_style_data_test.cc
@@ -696,5 +696,36 @@ EXPECT_EQ(*style1 == *style2, false); } +TEST(CSSDeclaredStyleDataTest, CanClearLongHandPropertyValueAndImportance) { + scoped_refptr<CSSDeclaredStyleData> style = new CSSDeclaredStyleData(); + style->SetPropertyValueAndImportance(kBorderLeftWidthProperty, + KeywordValue::GetInherit(), true); + style->ClearPropertyValueAndImportance(kBorderLeftWidthProperty); + + scoped_refptr<CSSDeclaredStyleData> empty_style = new CSSDeclaredStyleData(); + EXPECT_EQ(*style, *empty_style); +} + +TEST(CSSDeclaredStyleDataTest, CanClearPropertyValueAndImportance) { + scoped_refptr<CSSDeclaredStyleData> style = new CSSDeclaredStyleData(); + style->SetPropertyValueAndImportance(kBorderLeftWidthProperty, + KeywordValue::GetInherit(), true); + style->ClearPropertyValueAndImportance(kBorderWidthProperty); + + scoped_refptr<CSSDeclaredStyleData> empty_style = new CSSDeclaredStyleData(); + EXPECT_EQ(*style, *empty_style); +} + +TEST(CSSDeclaredStyleDataTest, + CanClearPropertyValueAndImportanceWithRecursion) { + scoped_refptr<CSSDeclaredStyleData> style = new CSSDeclaredStyleData(); + style->SetPropertyValueAndImportance(kBorderLeftWidthProperty, + KeywordValue::GetInherit(), true); + style->ClearPropertyValueAndImportance(kBorderProperty); + + scoped_refptr<CSSDeclaredStyleData> empty_style = new CSSDeclaredStyleData(); + EXPECT_EQ(*style, *empty_style); +} + } // namespace cssom } // namespace cobalt
diff --git a/src/cobalt/cssom/css_declared_style_declaration.cc b/src/cobalt/cssom/css_declared_style_declaration.cc index c1d455c..2e9b046 100644 --- a/src/cobalt/cssom/css_declared_style_declaration.cc +++ b/src/cobalt/cssom/css_declared_style_declaration.cc
@@ -16,6 +16,7 @@ #include "cobalt/cssom/css_declared_style_declaration.h" +#include "base/debug/trace_event.h" #include "base/lazy_instance.h" #include "cobalt/cssom/css_declared_style_data.h" #include "cobalt/cssom/css_parser.h" @@ -63,6 +64,7 @@ void CSSDeclaredStyleDeclaration::set_css_text( const std::string& css_text, script::ExceptionState* /*exception_state*/) { + TRACE_EVENT0("cobalt::cssom", "CSSDeclaredStyleDeclaration::set_css_text"); DCHECK(css_parser_); scoped_refptr<CSSDeclaredStyleData> declaration = css_parser_->ParseStyleDeclarationList( @@ -123,6 +125,22 @@ RecordMutation(); } +void CSSDeclaredStyleDeclaration::SetProperty( + const std::string& property_name, const std::string& property_value, + const std::string& priority, script::ExceptionState* /*exception_state*/) { + DLOG(INFO) << "CSSDeclaredStyleDeclaration::SetProperty(" << property_name + << "," << property_value << "," << priority << ")"; + DCHECK(css_parser_); + if (!data_) { + data_ = new CSSDeclaredStyleData(); + } + css_parser_->ParsePropertyIntoDeclarationData( + property_name, property_value, non_trivial_static_fields.Get().location, + data_.get()); + + RecordMutation(); +} + void CSSDeclaredStyleDeclaration::RecordMutation() { if (mutation_observer_) { // Trigger layout update.
diff --git a/src/cobalt/cssom/css_declared_style_declaration.h b/src/cobalt/cssom/css_declared_style_declaration.h index 344513d..9466df9 100644 --- a/src/cobalt/cssom/css_declared_style_declaration.h +++ b/src/cobalt/cssom/css_declared_style_declaration.h
@@ -39,6 +39,8 @@ // for declared styles, such as css style rules and inline styles. class CSSDeclaredStyleDeclaration : public CSSStyleDeclaration { public: + using CSSStyleDeclaration::SetProperty; + explicit CSSDeclaredStyleDeclaration(CSSParser* css_parser); CSSDeclaredStyleDeclaration(const scoped_refptr<CSSDeclaredStyleData>& data, @@ -58,6 +60,11 @@ const std::string& property_value, script::ExceptionState* exception_state) OVERRIDE; + void SetProperty(const std::string& property_name, + const std::string& property_value, + const std::string& priority, + script::ExceptionState* exception_state) OVERRIDE; + // Custom. const scoped_refptr<CSSDeclaredStyleData>& data() const { return data_; }
diff --git a/src/cobalt/cssom/css_declared_style_declaration_test.cc b/src/cobalt/cssom/css_declared_style_declaration_test.cc new file mode 100644 index 0000000..fb8d97d --- /dev/null +++ b/src/cobalt/cssom/css_declared_style_declaration_test.cc
@@ -0,0 +1,1525 @@ +/* + * Copyright 2016 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/cssom/css_declared_style_data.h" +#include "cobalt/cssom/css_declared_style_declaration.h" +#include "cobalt/cssom/css_parser.h" +#include "cobalt/cssom/css_style_rule.h" +#include "cobalt/cssom/css_style_sheet.h" +#include "cobalt/cssom/keyword_value.h" +#include "cobalt/cssom/length_value.h" +#include "cobalt/cssom/mutation_observer.h" +#include "cobalt/cssom/percentage_value.h" +#include "cobalt/cssom/property_definitions.h" +#include "cobalt/cssom/testing/mock_css_parser.h" +#include "cobalt/script/exception_state.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cobalt { +namespace cssom { + +using ::testing::_; +using ::testing::Return; + +namespace { +class MockMutationObserver : public MutationObserver { + public: + MOCK_METHOD0(OnCSSMutation, void()); +}; +} // namespace + +TEST(CSSDeclaredStyleDeclarationTest, BackgroundSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background = "rgba(0, 0, 0, .8)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundProperty), background, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_background(background, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BackgroundColorSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background_color = "#fff"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundColorProperty), + background_color, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_background_color(background_color, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BackgroundImageSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background_image = "url('images/sample.png')"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundImageProperty), + background_image, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_background_image(background_image, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BackgroundPositionSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background_position = "50% 50%"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundPositionProperty), + background_position, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_background_position(background_position, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BackgroundRepeatSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background_repeat = "no-repeat"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundRepeatProperty), + background_repeat, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_background_repeat(background_repeat, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BackgroundSizeSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background_size = "cover"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundSizeProperty), + background_size, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_background_size(background_size, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderProperty), border, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border(border, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderBottomSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_bottom = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderBottomProperty), border_bottom, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_bottom(border_bottom, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderBottomColorSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_bottom_color = "#010203"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderBottomColorProperty), + border_bottom_color, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_bottom_color(border_bottom_color, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderBottomStyleSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_bottom_style = "solid"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderBottomStyleProperty), + border_bottom_style, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_bottom_style(border_bottom_style, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderBottomWidthSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> width = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_bottom_width = "10px"; + MockMutationObserver observer; + width->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderBottomWidthProperty), + border_bottom_width, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + width->set_border_bottom_width(border_bottom_width, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderColorSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_color = "#010203"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderColorProperty), border_color, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_color(border_color, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderLeftSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_left = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderLeftProperty), border_left, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_left(border_left, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderLeftColorSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_left_color = "#010203"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderLeftColorProperty), + border_left_color, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_left_color(border_left_color, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderLeftStyleSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_left_style = "solid"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderLeftStyleProperty), + border_left_style, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_left_style(border_left_style, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderLeftWidthSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> width = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_left_width = "10px"; + MockMutationObserver observer; + width->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderLeftWidthProperty), + border_left_width, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + width->set_border_left_width(border_left_width, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderRadiusSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_radius = "0.2em"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderRadiusProperty), border_radius, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_radius(border_radius, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderRightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_right = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderRightProperty), border_right, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_right(border_right, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderRightColorSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_right_color = "#010203"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderRightColorProperty), + border_right_color, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_right_color(border_right_color, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderRightStyleSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_right_style = "solid"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderRightStyleProperty), + border_right_style, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_right_style(border_right_style, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderRightWidthSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> width = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_right_width = "10px"; + MockMutationObserver observer; + width->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderRightWidthProperty), + border_right_width, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + width->set_border_right_width(border_right_width, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderStyleSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_style = "solid"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderStyleProperty), border_style, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_style(border_style, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderTopSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_top = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderTopProperty), border_top, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_top(border_top, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderTopColorSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_top_color = "#010203"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderTopColorProperty), + border_top_color, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_top_color(border_top_color, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderTopStyleSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_top_style = "solid"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderTopStyleProperty), + border_top_style, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_border_top_style(border_top_style, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BorderTopWidthSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> width = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string border_top_width = "10px"; + MockMutationObserver observer; + width->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBorderTopWidthProperty), + border_top_width, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + width->set_border_top_width(border_top_width, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BottomSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string bottom = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kBottomProperty), bottom, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_bottom(bottom, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, BoxShadowSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string box_shadow = "none"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBoxShadowProperty), box_shadow, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_box_shadow(box_shadow, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, ColorSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string color = "rgba(0, 0, 0, .8)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kColorProperty), color, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_color(color, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, ContentSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string content = "url(foo.png)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kContentProperty), content, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_content(content, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, DisplaySetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string display = "inline-block"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kDisplayProperty), display, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_display(display, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, FontSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string font = "inherit"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kFontProperty), font, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_font(font, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, FontFamilySetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string font_family = "Roboto"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kFontFamilyProperty), font_family, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_font_family(font_family, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, FontSizeSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string font_size = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kFontSizeProperty), font_size, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_font_size(font_size, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, FontStyleSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string font_style = "italic"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kFontStyleProperty), font_style, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_font_style(font_style, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, FontWeightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string font_weight = "normal"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kFontWeightProperty), font_weight, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_font_weight(font_weight, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, HeightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string height = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kHeightProperty), height, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_height(height, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, LeftSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string left = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kLeftProperty), left, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_left(left, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, LineHeightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string line_height = "1.5em"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kLineHeightProperty), line_height, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_line_height(line_height, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MarginSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string margin = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kMarginProperty), margin, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_margin(margin, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MarginBottomSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string margin_bottom = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kMarginBottomProperty), margin_bottom, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_margin_bottom(margin_bottom, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MarginLeftSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string margin_left = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kMarginLeftProperty), margin_left, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_margin_left(margin_left, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MarginRightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string margin_right = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kMarginRightProperty), margin_right, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_margin_right(margin_right, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MarginTopSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string margin_top = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kMarginTopProperty), margin_top, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_margin_top(margin_top, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MaxHeightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string max_height = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kMaxHeightProperty), max_height, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_max_height(max_height, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MaxWidthSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string max_width = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kMaxWidthProperty), max_width, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_max_width(max_width, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MinHeightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string min_height = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kMinHeightProperty), min_height, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_min_height(min_height, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, MinWidthSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string min_width = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kMinWidthProperty), min_width, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_min_width(min_width, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, OpacitySetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string opacity = "0.5"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kOpacityProperty), opacity, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_opacity(opacity, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, OverflowSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string overflow = "visible"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kOverflowProperty), overflow, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_overflow(overflow, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, OverflowWrapSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string overflow_wrap = "break-word"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kOverflowWrapProperty), overflow_wrap, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_overflow_wrap(overflow_wrap, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PaddingSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string padding = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kPaddingProperty), padding, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_padding(padding, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PaddingBottomSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string padding_bottom = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kPaddingBottomProperty), + padding_bottom, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_padding_bottom(padding_bottom, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PaddingLeftSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string padding_left = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kPaddingLeftProperty), padding_left, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_padding_left(padding_left, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PaddingRightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string padding_right = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kPaddingRightProperty), padding_right, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_padding_right(padding_right, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PaddingTopSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string padding_top = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kPaddingTopProperty), padding_top, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_padding_top(padding_top, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PositionSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string position = "static"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kPositionProperty), position, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_position(position, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, RightSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string right = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kRightProperty), right, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_right(right, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TextAlignSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string text_align = "center"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kTextAlignProperty), text_align, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_text_align(text_align, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TextDecorationColorSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string text_decoration_color = "rgba(0, 0, 0, .8)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kTextDecorationColorProperty), + text_decoration_color, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_text_decoration_color(text_decoration_color, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TextDecorationLineSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string text_decoration_line = "line-through"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kTextDecorationLineProperty), + text_decoration_line, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_text_decoration_line(text_decoration_line, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TextIndentSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string text_indent = "4em"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kTextIndentProperty), text_indent, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_text_indent(text_indent, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TextOverflowSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string text_overflow = "ellipsis"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kTextOverflowProperty), text_overflow, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_text_overflow(text_overflow, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TextShadowSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string text_shadow = "inherit"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kTextShadowProperty), text_shadow, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_text_shadow(text_shadow, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TextTransformSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string text_transform = "uppercase"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kTextTransformProperty), + text_transform, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_text_transform(text_transform, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TopSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string top = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kTopProperty), top, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_top(top, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TransformSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string transform = "scale(1.5)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kTransformProperty), transform, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_transform(transform, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TransformOriginSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string transform_origin = "20% 40%"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kTransformOriginProperty), + transform_origin, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_transform_origin(transform_origin, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TransitionSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string transition = "inherit"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kTransitionProperty), transition, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_transition(transition, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TransitionDelaySetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string transition_delay = "2s"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kTransitionDelayProperty), + transition_delay, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_transition_delay(transition_delay, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TransitionDurationSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string transition_duration = "2s"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kTransitionDurationProperty), + transition_duration, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_transition_duration(transition_duration, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TransitionPropertySetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string transition_property = "width"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kTransitionPropertyProperty), + transition_property, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_transition_property(transition_property, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, TransitionTimingFunctionSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string transition_timing_function = "linear"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kTransitionTimingFunctionProperty), + transition_timing_function, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_transition_timing_function(transition_timing_function, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, VerticalAlignSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string vertical_align = "middle"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kVerticalAlignProperty), + vertical_align, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_vertical_align(vertical_align, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, VisibilitySetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string visibility = "hidden"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kVisibilityProperty), visibility, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_visibility(visibility, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, WhiteSpaceSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string white_space = "pre"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kWhiteSpaceProperty), white_space, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_white_space(white_space, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, WidthSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string width = "100px"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kWidthProperty), width, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_width(width, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, WordWrapSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string word_wrap = "break-word"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kWordWrapProperty), word_wrap, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_word_wrap(word_wrap, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, ZIndexSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string z_index = "-1"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( + GetPropertyName(kZIndexProperty), z_index, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + + style->set_z_index(z_index, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, CSSTextSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string css_text = "font-size: 100px; color: #0047ab;"; + + EXPECT_CALL(css_parser, ParseStyleDeclarationList(css_text, _)) + .WillOnce(Return(scoped_refptr<CSSDeclaredStyleData>())); + style->set_css_text(css_text, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, CSSTextSetterEmptyString) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleData> initial_style = + new CSSDeclaredStyleData(); + initial_style->SetPropertyValueAndImportance( + kDisplayProperty, KeywordValue::GetInline(), false); + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(initial_style, &css_parser); + + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + const std::string css_text = ""; + + EXPECT_CALL(css_parser, ParseStyleDeclarationList(css_text, _)) + .WillOnce(Return(scoped_refptr<CSSDeclaredStyleData>())); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->set_css_text(css_text, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, CSSTextGetter) { + testing::MockCSSParser css_parser; + scoped_refptr<PercentageValue> background_size = new PercentageValue(0.50f); + scoped_refptr<LengthValue> bottom = new LengthValue(16, kPixelsUnit); + + scoped_refptr<CSSDeclaredStyleData> style_data = new CSSDeclaredStyleData(); + style_data->SetPropertyValueAndImportance(kBackgroundSizeProperty, + background_size, false); + style_data->SetPropertyValueAndImportance(kBottomProperty, bottom, true); + + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(style_data, &css_parser); + EXPECT_EQ(style->css_text(NULL), + "background-size: 50%; bottom: 16px !important;"); +} + +// TODO: Add GetPropertyValue tests, property getter tests and tests +// that checking if the attributes' setter and the getter are consistent when +// fully support converting PropertyValue to std::string. +TEST(CSSDeclaredStyleDeclarationTest, PropertyValueSetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background = "rgba(0, 0, 0, .8)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundProperty), background, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->SetPropertyValue(GetPropertyName(kBackgroundProperty), background, + NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PropertyValueSetterTwice) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background = "rgba(0, 0, 0, .8)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundProperty), background, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->SetPropertyValue(GetPropertyName(kBackgroundProperty), background, + NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PropertySetterWithTwoValues) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background = "rgba(0, 0, 0, .8)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundProperty), background, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->SetProperty(GetPropertyName(kBackgroundProperty), background, NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PropertySetterWithThreeValues) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + const std::string background = "rgba(0, 0, 0, .8)"; + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kBackgroundProperty), background, _, _)); + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + style->SetProperty(GetPropertyName(kBackgroundProperty), background, + std::string(), NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, PropertyValueGetter) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleData> initial_style = + new CSSDeclaredStyleData(); + initial_style->SetPropertyValueAndImportance( + kTextAlignProperty, KeywordValue::GetCenter(), false); + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(initial_style, &css_parser); + + EXPECT_EQ(style->GetPropertyValue(GetPropertyName(kTextAlignProperty)), + "center"); +} + +TEST(CSSDeclaredStyleDeclarationTest, + UnknownDeclaredStylePropertyValueShouldBeEmpty) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleData> initial_style = + new CSSDeclaredStyleData(); + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(initial_style, &css_parser); + + EXPECT_EQ(style->GetPropertyValue("cobalt_cobalt_cobalt"), ""); +} + +TEST(CSSDeclaredStyleDeclarationTest, RemoveProperty) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleData> initial_style = + new CSSDeclaredStyleData(); + initial_style->SetPropertyValueAndImportance( + kDisplayProperty, KeywordValue::GetInline(), false); + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(initial_style, &css_parser); + + MockMutationObserver observer; + style->set_mutation_observer(&observer); + + EXPECT_CALL(observer, OnCSSMutation()).Times(1); + EXPECT_CALL(css_parser, + ParsePropertyIntoDeclarationData( + GetPropertyName(kDisplayProperty), std::string(""), _, _)); + style->RemoveProperty(GetPropertyName(kDisplayProperty), NULL); +} + +TEST(CSSDeclaredStyleDeclarationTest, LengthAttributeGetterEmpty) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + EXPECT_EQ(style->length(), 0); +} + +TEST(CSSDeclaredStyleDeclarationTest, LengthAttributeGetterNotEmpty) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleData> initial_style = + new CSSDeclaredStyleData(); + initial_style->SetPropertyValueAndImportance( + kDisplayProperty, KeywordValue::GetInline(), false); + initial_style->SetPropertyValueAndImportance( + kTextAlignProperty, KeywordValue::GetCenter(), false); + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(initial_style, &css_parser); + + EXPECT_EQ(style->length(), 2); +} + +TEST(CSSDeclaredStyleDeclarationTest, ItemGetterEmpty) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(&css_parser); + + EXPECT_FALSE(style->Item(0)); +} + +TEST(CSSDeclaredStyleDeclarationTest, ItemGetterNotEmpty) { + testing::MockCSSParser css_parser; + scoped_refptr<CSSDeclaredStyleData> initial_style = + new CSSDeclaredStyleData(); + initial_style->SetPropertyValueAndImportance( + kDisplayProperty, KeywordValue::GetInline(), false); + initial_style->SetPropertyValueAndImportance( + kTextAlignProperty, KeywordValue::GetCenter(), false); + scoped_refptr<CSSDeclaredStyleDeclaration> style = + new CSSDeclaredStyleDeclaration(initial_style, &css_parser); + + EXPECT_TRUE(style->Item(0)); + EXPECT_TRUE(style->Item(1)); + EXPECT_FALSE(style->Item(2)); + + // The order is not important, as long as declared properties are represented. + if (style->Item(0).value() == GetPropertyName(kDisplayProperty)) { + EXPECT_EQ(style->Item(1).value(), GetPropertyName(kTextAlignProperty)); + } else { + EXPECT_EQ(style->Item(0).value(), GetPropertyName(kTextAlignProperty)); + EXPECT_EQ(style->Item(1).value(), GetPropertyName(kDisplayProperty)); + } +} + +} // namespace cssom +} // namespace cobalt
diff --git a/src/cobalt/cssom/css_style_declaration.h b/src/cobalt/cssom/css_style_declaration.h index 6bfcd8b..dc542d2 100644 --- a/src/cobalt/cssom/css_style_declaration.h +++ b/src/cobalt/cssom/css_style_declaration.h
@@ -427,6 +427,26 @@ const std::string& property_value, script::ExceptionState* exception_state) = 0; + virtual void SetProperty(const std::string& property_name, + const std::string& property_value, + const std::string& priority, + script::ExceptionState* exception_state) = 0; + + void SetProperty(const std::string& property_name, + const std::string& property_value, + script::ExceptionState* exception_state) { + SetPropertyValue(property_name, property_value, exception_state); + } + + std::string RemoveProperty(const std::string& property_name, + script::ExceptionState* exception_state) { + std::string retval = GetPropertyValue(property_name); + if (!retval.empty()) { + SetPropertyValue(property_name, "", exception_state); + } + return retval; + } + // The parent rule is the CSS rule that the CSS declaration block is // associated with, if any, or null otherwise. // https://www.w3.org/TR/2013/WD-cssom-20131205/#css-declaration-block
diff --git a/src/cobalt/cssom/css_style_declaration_test.cc b/src/cobalt/cssom/css_style_declaration_test.cc deleted file mode 100644 index 0e71d24..0000000 --- a/src/cobalt/cssom/css_style_declaration_test.cc +++ /dev/null
@@ -1,1455 +0,0 @@ -/* - * Copyright 2014 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/cssom/css_style_declaration.h" - -#include "cobalt/cssom/css_declared_style_data.h" -#include "cobalt/cssom/css_declared_style_declaration.h" -#include "cobalt/cssom/css_parser.h" -#include "cobalt/cssom/css_style_rule.h" -#include "cobalt/cssom/css_style_sheet.h" -#include "cobalt/cssom/keyword_value.h" -#include "cobalt/cssom/length_value.h" -#include "cobalt/cssom/mutation_observer.h" -#include "cobalt/cssom/percentage_value.h" -#include "cobalt/cssom/property_definitions.h" -#include "cobalt/cssom/testing/mock_css_parser.h" -#include "cobalt/script/exception_state.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace cobalt { -namespace cssom { - -using ::testing::_; -using ::testing::Return; - -class MockMutationObserver : public MutationObserver { - public: - MOCK_METHOD0(OnCSSMutation, void()); -}; - -TEST(CSSStyleDeclarationTest, BackgroundSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string background = "rgba(0, 0, 0, .8)"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBackgroundProperty), background, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_background(background, NULL); -} - -TEST(CSSStyleDeclarationTest, BackgroundColorSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string background_color = "#fff"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBackgroundColorProperty), - background_color, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_background_color(background_color, NULL); -} - -TEST(CSSStyleDeclarationTest, BackgroundImageSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string background_image = "url('images/sample.png')"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBackgroundImageProperty), - background_image, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_background_image(background_image, NULL); -} - -TEST(CSSStyleDeclarationTest, BackgroundPositionSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string background_position = "50% 50%"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBackgroundPositionProperty), - background_position, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_background_position(background_position, NULL); -} - -TEST(CSSStyleDeclarationTest, BackgroundRepeatSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string background_repeat = "no-repeat"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBackgroundRepeatProperty), - background_repeat, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_background_repeat(background_repeat, NULL); -} - -TEST(CSSStyleDeclarationTest, BackgroundSizeSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string background_size = "cover"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBackgroundSizeProperty), - background_size, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_background_size(background_size, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderProperty), border, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border(border, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderBottomSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_bottom = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderBottomProperty), border_bottom, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_bottom(border_bottom, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderBottomColorSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_bottom_color = "#010203"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderBottomColorProperty), - border_bottom_color, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_bottom_color(border_bottom_color, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderBottomStyleSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_bottom_style = "solid"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderBottomStyleProperty), - border_bottom_style, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_bottom_style(border_bottom_style, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderBottomWidthSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> width = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_bottom_width = "10px"; - MockMutationObserver observer; - width->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderBottomWidthProperty), - border_bottom_width, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - width->set_border_bottom_width(border_bottom_width, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderColorSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_color = "#010203"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderColorProperty), border_color, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_color(border_color, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderLeftSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_left = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderLeftProperty), border_left, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_left(border_left, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderLeftColorSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_left_color = "#010203"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderLeftColorProperty), - border_left_color, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_left_color(border_left_color, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderLeftStyleSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_left_style = "solid"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderLeftStyleProperty), - border_left_style, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_left_style(border_left_style, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderLeftWidthSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> width = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_left_width = "10px"; - MockMutationObserver observer; - width->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderLeftWidthProperty), - border_left_width, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - width->set_border_left_width(border_left_width, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderRadiusSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_radius = "0.2em"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderRadiusProperty), border_radius, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_radius(border_radius, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderRightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_right = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderRightProperty), border_right, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_right(border_right, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderRightColorSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_right_color = "#010203"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderRightColorProperty), - border_right_color, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_right_color(border_right_color, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderRightStyleSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_right_style = "solid"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderRightStyleProperty), - border_right_style, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_right_style(border_right_style, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderRightWidthSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> width = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_right_width = "10px"; - MockMutationObserver observer; - width->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderRightWidthProperty), - border_right_width, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - width->set_border_right_width(border_right_width, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderStyleSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_style = "solid"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderStyleProperty), border_style, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_style(border_style, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderTopSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_top = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderTopProperty), border_top, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_top(border_top, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderTopColorSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_top_color = "#010203"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderTopColorProperty), - border_top_color, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_top_color(border_top_color, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderTopStyleSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_top_style = "solid"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderTopStyleProperty), - border_top_style, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_border_top_style(border_top_style, NULL); -} - -TEST(CSSStyleDeclarationTest, BorderTopWidthSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> width = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string border_top_width = "10px"; - MockMutationObserver observer; - width->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBorderTopWidthProperty), - border_top_width, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - width->set_border_top_width(border_top_width, NULL); -} - -TEST(CSSStyleDeclarationTest, BottomSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string bottom = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kBottomProperty), bottom, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_bottom(bottom, NULL); -} - -TEST(CSSStyleDeclarationTest, BoxShadowSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string box_shadow = "none"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBoxShadowProperty), box_shadow, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_box_shadow(box_shadow, NULL); -} - -TEST(CSSStyleDeclarationTest, ColorSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string color = "rgba(0, 0, 0, .8)"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kColorProperty), color, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_color(color, NULL); -} - -TEST(CSSStyleDeclarationTest, ContentSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string content = "url(foo.png)"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kContentProperty), content, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_content(content, NULL); -} - -TEST(CSSStyleDeclarationTest, DisplaySetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string display = "inline-block"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kDisplayProperty), display, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_display(display, NULL); -} - -TEST(CSSStyleDeclarationTest, FontSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string font = "inherit"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kFontProperty), font, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_font(font, NULL); -} - -TEST(CSSStyleDeclarationTest, FontFamilySetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string font_family = "Roboto"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kFontFamilyProperty), font_family, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_font_family(font_family, NULL); -} - -TEST(CSSStyleDeclarationTest, FontSizeSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string font_size = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kFontSizeProperty), font_size, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_font_size(font_size, NULL); -} - -TEST(CSSStyleDeclarationTest, FontStyleSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string font_style = "italic"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kFontStyleProperty), font_style, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_font_style(font_style, NULL); -} - -TEST(CSSStyleDeclarationTest, FontWeightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string font_weight = "normal"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kFontWeightProperty), font_weight, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_font_weight(font_weight, NULL); -} - -TEST(CSSStyleDeclarationTest, HeightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string height = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kHeightProperty), height, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_height(height, NULL); -} - -TEST(CSSStyleDeclarationTest, LeftSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string left = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kLeftProperty), left, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_left(left, NULL); -} - -TEST(CSSStyleDeclarationTest, LineHeightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string line_height = "1.5em"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kLineHeightProperty), line_height, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_line_height(line_height, NULL); -} - -TEST(CSSStyleDeclarationTest, MarginSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string margin = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kMarginProperty), margin, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_margin(margin, NULL); -} - -TEST(CSSStyleDeclarationTest, MarginBottomSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string margin_bottom = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kMarginBottomProperty), margin_bottom, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_margin_bottom(margin_bottom, NULL); -} - -TEST(CSSStyleDeclarationTest, MarginLeftSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string margin_left = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kMarginLeftProperty), margin_left, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_margin_left(margin_left, NULL); -} - -TEST(CSSStyleDeclarationTest, MarginRightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string margin_right = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kMarginRightProperty), margin_right, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_margin_right(margin_right, NULL); -} - -TEST(CSSStyleDeclarationTest, MarginTopSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string margin_top = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kMarginTopProperty), margin_top, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_margin_top(margin_top, NULL); -} - -TEST(CSSStyleDeclarationTest, MaxHeightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string max_height = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kMaxHeightProperty), max_height, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_max_height(max_height, NULL); -} - -TEST(CSSStyleDeclarationTest, MaxWidthSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string max_width = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kMaxWidthProperty), max_width, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_max_width(max_width, NULL); -} - -TEST(CSSStyleDeclarationTest, MinHeightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string min_height = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kMinHeightProperty), min_height, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_min_height(min_height, NULL); -} - -TEST(CSSStyleDeclarationTest, MinWidthSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string min_width = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kMinWidthProperty), min_width, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_min_width(min_width, NULL); -} - -TEST(CSSStyleDeclarationTest, OpacitySetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string opacity = "0.5"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kOpacityProperty), opacity, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_opacity(opacity, NULL); -} - -TEST(CSSStyleDeclarationTest, OverflowSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string overflow = "visible"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kOverflowProperty), overflow, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_overflow(overflow, NULL); -} - -TEST(CSSStyleDeclarationTest, OverflowWrapSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string overflow_wrap = "break-word"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kOverflowWrapProperty), overflow_wrap, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_overflow_wrap(overflow_wrap, NULL); -} - -TEST(CSSStyleDeclarationTest, PaddingSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string padding = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kPaddingProperty), padding, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_padding(padding, NULL); -} - -TEST(CSSStyleDeclarationTest, PaddingBottomSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string padding_bottom = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kPaddingBottomProperty), - padding_bottom, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_padding_bottom(padding_bottom, NULL); -} - -TEST(CSSStyleDeclarationTest, PaddingLeftSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string padding_left = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kPaddingLeftProperty), padding_left, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_padding_left(padding_left, NULL); -} - -TEST(CSSStyleDeclarationTest, PaddingRightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string padding_right = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kPaddingRightProperty), padding_right, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_padding_right(padding_right, NULL); -} - -TEST(CSSStyleDeclarationTest, PaddingTopSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string padding_top = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kPaddingTopProperty), padding_top, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_padding_top(padding_top, NULL); -} - -TEST(CSSStyleDeclarationTest, PositionSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string position = "static"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kPositionProperty), position, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_position(position, NULL); -} - -TEST(CSSStyleDeclarationTest, RightSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string right = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kRightProperty), right, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_right(right, NULL); -} - -TEST(CSSStyleDeclarationTest, TextAlignSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string text_align = "center"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kTextAlignProperty), text_align, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_text_align(text_align, NULL); -} - -TEST(CSSStyleDeclarationTest, TextDecorationColorSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string text_decoration_color = "rgba(0, 0, 0, .8)"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kTextDecorationColorProperty), - text_decoration_color, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_text_decoration_color(text_decoration_color, NULL); -} - -TEST(CSSStyleDeclarationTest, TextDecorationLineSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string text_decoration_line = "line-through"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kTextDecorationLineProperty), - text_decoration_line, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_text_decoration_line(text_decoration_line, NULL); -} - -TEST(CSSStyleDeclarationTest, TextIndentSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string text_indent = "4em"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kTextIndentProperty), text_indent, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_text_indent(text_indent, NULL); -} - -TEST(CSSStyleDeclarationTest, TextOverflowSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string text_overflow = "ellipsis"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kTextOverflowProperty), text_overflow, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_text_overflow(text_overflow, NULL); -} - -TEST(CSSStyleDeclarationTest, TextShadowSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string text_shadow = "inherit"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kTextShadowProperty), text_shadow, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_text_shadow(text_shadow, NULL); -} - -TEST(CSSStyleDeclarationTest, TextTransformSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string text_transform = "uppercase"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kTextTransformProperty), - text_transform, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_text_transform(text_transform, NULL); -} - -TEST(CSSStyleDeclarationTest, TopSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string top = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kTopProperty), top, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_top(top, NULL); -} - -TEST(CSSStyleDeclarationTest, TransformSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string transform = "scale(1.5)"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kTransformProperty), transform, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_transform(transform, NULL); -} - -TEST(CSSStyleDeclarationTest, TransformOriginSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string transform_origin = "20% 40%"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kTransformOriginProperty), - transform_origin, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_transform_origin(transform_origin, NULL); -} - -TEST(CSSStyleDeclarationTest, TransitionSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string transition = "inherit"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kTransitionProperty), transition, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_transition(transition, NULL); -} - -TEST(CSSStyleDeclarationTest, TransitionDelaySetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string transition_delay = "2s"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kTransitionDelayProperty), - transition_delay, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_transition_delay(transition_delay, NULL); -} - -TEST(CSSStyleDeclarationTest, TransitionDurationSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string transition_duration = "2s"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kTransitionDurationProperty), - transition_duration, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_transition_duration(transition_duration, NULL); -} - -TEST(CSSStyleDeclarationTest, TransitionPropertySetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string transition_property = "width"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kTransitionPropertyProperty), - transition_property, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_transition_property(transition_property, NULL); -} - -TEST(CSSStyleDeclarationTest, TransitionTimingFunctionSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string transition_timing_function = "linear"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kTransitionTimingFunctionProperty), - transition_timing_function, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_transition_timing_function(transition_timing_function, NULL); -} - -TEST(CSSStyleDeclarationTest, VerticalAlignSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string vertical_align = "middle"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kVerticalAlignProperty), - vertical_align, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_vertical_align(vertical_align, NULL); -} - -TEST(CSSStyleDeclarationTest, VisibilitySetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string visibility = "hidden"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kVisibilityProperty), visibility, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_visibility(visibility, NULL); -} - -TEST(CSSStyleDeclarationTest, WhiteSpaceSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string white_space = "pre"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kWhiteSpaceProperty), white_space, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_white_space(white_space, NULL); -} - -TEST(CSSStyleDeclarationTest, WidthSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string width = "100px"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kWidthProperty), width, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_width(width, NULL); -} - -TEST(CSSStyleDeclarationTest, WordWrapSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string word_wrap = "break-word"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kWordWrapProperty), word_wrap, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_word_wrap(word_wrap, NULL); -} - -TEST(CSSStyleDeclarationTest, ZIndexSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string z_index = "-1"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, ParsePropertyIntoDeclarationData( - GetPropertyName(kZIndexProperty), z_index, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - - style->set_z_index(z_index, NULL); -} - -TEST(CSSStyleDeclarationTest, CSSTextSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string css_text = "font-size: 100px; color: #0047ab;"; - - EXPECT_CALL(css_parser, ParseStyleDeclarationList(css_text, _)) - .WillOnce(Return(scoped_refptr<CSSDeclaredStyleData>())); - style->set_css_text(css_text, NULL); -} - -TEST(CSSStyleDeclarationTest, CSSTextSetterEmptyString) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleData> initial_style = - new CSSDeclaredStyleData(); - initial_style->SetPropertyValueAndImportance( - kDisplayProperty, KeywordValue::GetInline(), false); - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(initial_style, &css_parser); - - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - const std::string css_text = ""; - - EXPECT_CALL(css_parser, ParseStyleDeclarationList(css_text, _)) - .WillOnce(Return(scoped_refptr<CSSDeclaredStyleData>())); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->set_css_text(css_text, NULL); -} - -TEST(CSSStyleDeclarationTest, CSSTextGetter) { - testing::MockCSSParser css_parser; - scoped_refptr<PercentageValue> background_size = new PercentageValue(0.50f); - scoped_refptr<LengthValue> bottom = new LengthValue(16, kPixelsUnit); - - scoped_refptr<CSSDeclaredStyleData> style_data = new CSSDeclaredStyleData(); - style_data->SetPropertyValueAndImportance(kBackgroundSizeProperty, - background_size, false); - style_data->SetPropertyValueAndImportance(kBottomProperty, bottom, true); - - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(style_data, &css_parser); - EXPECT_EQ(style->css_text(NULL), - "background-size: 50%; bottom: 16px !important;"); -} - -// TODO: Add GetPropertyValue tests, property getter tests and tests -// that checking if the attributes' setter and the getter are consistent when -// fully support converting PropertyValue to std::string. -TEST(CSSStyleDeclarationTest, PropertyValueSetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - const std::string background = "rgba(0, 0, 0, .8)"; - MockMutationObserver observer; - style->set_mutation_observer(&observer); - - EXPECT_CALL(css_parser, - ParsePropertyIntoDeclarationData( - GetPropertyName(kBackgroundProperty), background, _, _)); - EXPECT_CALL(observer, OnCSSMutation()).Times(1); - style->SetPropertyValue(GetPropertyName(kBackgroundProperty), background, - NULL); -} - -TEST(CSSStyleDeclarationTest, PropertyValueGetter) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleData> initial_style = - new CSSDeclaredStyleData(); - initial_style->SetPropertyValueAndImportance( - kTextAlignProperty, KeywordValue::GetCenter(), false); - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(initial_style, &css_parser); - - EXPECT_EQ(style->GetPropertyValue(GetPropertyName(kTextAlignProperty)), - "center"); -} - -TEST(CSSStyleDeclarationTest, UnknownDeclaredStylePropertyValueShouldBeEmpty) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleData> initial_style = - new CSSDeclaredStyleData(); - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(initial_style, &css_parser); - - EXPECT_EQ(style->GetPropertyValue("cobalt_cobalt_cobalt"), ""); -} - -TEST(CSSStyleDeclarationTest, LengthAttributeGetterEmpty) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - EXPECT_EQ(style->length(), 0); -} - -TEST(CSSStyleDeclarationTest, LengthAttributeGetterNotEmpty) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleData> initial_style = - new CSSDeclaredStyleData(); - initial_style->SetPropertyValueAndImportance( - kDisplayProperty, KeywordValue::GetInline(), false); - initial_style->SetPropertyValueAndImportance( - kTextAlignProperty, KeywordValue::GetCenter(), false); - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(initial_style, &css_parser); - - EXPECT_EQ(style->length(), 2); -} - -TEST(CSSStyleDeclarationTest, ItemGetterEmpty) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(&css_parser); - - EXPECT_FALSE(style->Item(0)); -} - -TEST(CSSStyleDeclarationTest, ItemGetterNotEmpty) { - testing::MockCSSParser css_parser; - scoped_refptr<CSSDeclaredStyleData> initial_style = - new CSSDeclaredStyleData(); - initial_style->SetPropertyValueAndImportance( - kDisplayProperty, KeywordValue::GetInline(), false); - initial_style->SetPropertyValueAndImportance( - kTextAlignProperty, KeywordValue::GetCenter(), false); - scoped_refptr<CSSDeclaredStyleDeclaration> style = - new CSSDeclaredStyleDeclaration(initial_style, &css_parser); - - EXPECT_TRUE(style->Item(0)); - EXPECT_TRUE(style->Item(1)); - EXPECT_FALSE(style->Item(2)); - - // The order is not important, as long as with properties are represented. - if (style->Item(0).value() == GetPropertyName(kDisplayProperty)) { - EXPECT_EQ(style->Item(1).value(), GetPropertyName(kTextAlignProperty)); - } else { - EXPECT_EQ(style->Item(0).value(), GetPropertyName(kTextAlignProperty)); - EXPECT_EQ(style->Item(1).value(), GetPropertyName(kDisplayProperty)); - } -} - -} // namespace cssom -} // namespace cobalt
diff --git a/src/cobalt/cssom/cssom.gyp b/src/cobalt/cssom/cssom.gyp index 63be9ea..a3af6c4 100644 --- a/src/cobalt/cssom/cssom.gyp +++ b/src/cobalt/cssom/cssom.gyp
@@ -100,6 +100,9 @@ 'descendant_combinator.h', 'empty_pseudo_class.cc', 'empty_pseudo_class.h', + 'filter_function.h', + 'filter_function_list_value.cc', + 'filter_function_list_value.h', 'focus_pseudo_class.cc', 'focus_pseudo_class.h', 'following_sibling_combinator.cc', @@ -143,6 +146,8 @@ 'media_query.h', 'media_type_names.cc', 'media_type_names.h', + 'mtm_function.cc', + 'mtm_function.h', 'mutation_observer.h', 'next_sibling_combinator.cc', 'next_sibling_combinator.h',
diff --git a/src/cobalt/cssom/cssom_test.gyp b/src/cobalt/cssom/cssom_test.gyp index a776fa4..3c57b62 100644 --- a/src/cobalt/cssom/cssom_test.gyp +++ b/src/cobalt/cssom/cssom_test.gyp
@@ -26,14 +26,15 @@ 'computed_style_test.cc', 'css_computed_style_data_property_set_matcher_test.cc', 'css_computed_style_data_test.cc', + 'css_computed_style_declaration_test.cc', 'css_declared_style_data_test.cc', + 'css_declared_style_declaration_test.cc', 'css_font_face_declaration_data_test.cc', 'css_font_face_rule_test.cc', 'css_grouping_rule_test.cc', 'css_property_definitions_test.cc', 'css_rule_list_test.cc', 'css_rule_visitor_test.cc', - 'css_style_declaration_test.cc', 'css_style_sheet_test.cc', 'css_transition_set_test.cc', 'interpolate_property_value_test.cc',
diff --git a/src/cobalt/cssom/filter_function.h b/src/cobalt/cssom/filter_function.h new file mode 100644 index 0000000..c3053c0 --- /dev/null +++ b/src/cobalt/cssom/filter_function.h
@@ -0,0 +1,39 @@ +/* + * Copyright 2016 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_CSSOM_FILTER_FUNCTION_H_ +#define COBALT_CSSOM_FILTER_FUNCTION_H_ + +#include <string> + +#include "cobalt/base/polymorphic_equatable.h" + +namespace cobalt { +namespace cssom { + +// A base class for filter functions. Filters manipulate an element's rendering. +// https://www.w3.org/TR/filter-effects-1/ +class FilterFunction : public base::PolymorphicEquatable { + public: + virtual std::string ToString() const = 0; + + virtual ~FilterFunction() {} +}; + +} // namespace cssom +} // namespace cobalt + +#endif // COBALT_CSSOM_FILTER_FUNCTION_H_
diff --git a/src/cobalt/cssom/filter_function_list_value.cc b/src/cobalt/cssom/filter_function_list_value.cc new file mode 100644 index 0000000..443832b --- /dev/null +++ b/src/cobalt/cssom/filter_function_list_value.cc
@@ -0,0 +1,32 @@ +/* + * Copyright 2016 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/cssom/filter_function_list_value.h" + +namespace cobalt { +namespace cssom { + +std::string FilterFunctionListValue::ToString() const { + std::string result; + for (size_t i = 0; i < value().size(); ++i) { + if (!result.empty()) result.append(" "); + result.append(value()[i]->ToString()); + } + return result; +} + +} // namespace cssom +} // namespace cobalt
diff --git a/src/cobalt/cssom/filter_function_list_value.h b/src/cobalt/cssom/filter_function_list_value.h new file mode 100644 index 0000000..c1f137b --- /dev/null +++ b/src/cobalt/cssom/filter_function_list_value.h
@@ -0,0 +1,53 @@ +/* + * Copyright 2016 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_CSSOM_FILTER_FUNCTION_LIST_VALUE_H_ +#define COBALT_CSSOM_FILTER_FUNCTION_LIST_VALUE_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "cobalt/base/polymorphic_equatable.h" +#include "cobalt/cssom/filter_function.h" +#include "cobalt/cssom/property_value_visitor.h" +#include "cobalt/cssom/scoped_list_value.h" + +namespace cobalt { +namespace cssom { + +// A list of composed filter functions. +class FilterFunctionListValue : public ScopedListValue<FilterFunction> { + public: + explicit FilterFunctionListValue( + ScopedListValue<FilterFunction>::Builder value) + : ScopedListValue(value.Pass()) {} + + void Accept(PropertyValueVisitor* visitor) OVERRIDE { + visitor->VisitFilterFunctionList(this); + } + + std::string ToString() const OVERRIDE; + + DEFINE_POLYMORPHIC_EQUATABLE_TYPE(FilterFunctionListValue); + + private: + virtual ~FilterFunctionListValue() OVERRIDE {} +}; + +} // namespace cssom +} // namespace cobalt + +#endif // COBALT_CSSOM_FILTER_FUNCTION_LIST_VALUE_H_
diff --git a/src/cobalt/cssom/interpolate_property_value.cc b/src/cobalt/cssom/interpolate_property_value.cc index 061332d..d9bea8b 100644 --- a/src/cobalt/cssom/interpolate_property_value.cc +++ b/src/cobalt/cssom/interpolate_property_value.cc
@@ -70,6 +70,8 @@ void VisitAbsoluteURL(AbsoluteURLValue* start_absolute_url_value) OVERRIDE; void VisitCalc(CalcValue* start_calc_value) OVERRIDE; + void VisitFilterFunctionList( + FilterFunctionListValue* start_filter_function_list_value) OVERRIDE; void VisitFontStyle(FontStyleValue* start_font_style_value) OVERRIDE; void VisitFontWeight(FontWeightValue* start_font_weight_value) OVERRIDE; void VisitInteger(IntegerValue* integer_value) OVERRIDE; @@ -356,6 +358,12 @@ interpolated_value_ = end_value_; } +void InterpolateVisitor::VisitFilterFunctionList( + FilterFunctionListValue* /*start_filter_function_list_value*/) { + NOTIMPLEMENTED(); + interpolated_value_ = end_value_; +} + void InterpolateVisitor::VisitFontStyle( FontStyleValue* /*start_font_style_value*/) { NOTIMPLEMENTED();
diff --git a/src/cobalt/cssom/keyword_names.cc b/src/cobalt/cssom/keyword_names.cc index 28cbfbf..34fde90 100644 --- a/src/cobalt/cssom/keyword_names.cc +++ b/src/cobalt/cssom/keyword_names.cc
@@ -73,6 +73,7 @@ const char kLineThroughKeywordName[] = "line-through"; const char kMaroonKeywordName[] = "maroon"; const char kMiddleKeywordName[] = "middle"; +const char kMonoscopicKeywordName[] = "monoscopic"; const char kMonospaceKeywordName[] = "monospace"; const char kNavyKeywordName[] = "navy"; const char kNoneKeywordName[] = "none"; @@ -100,6 +101,8 @@ const char kStaticKeywordName[] = "static"; const char kStepEndKeywordName[] = "step-end"; const char kStepStartKeywordName[] = "step-start"; +const char kStereoscopicLeftRightKeywordName[] = "stereoscopic-left-right"; +const char kStereoscopicTopBottomKeywordName[] = "stereoscopic-top-bottom"; const char kTealKeywordName[] = "teal"; const char kToKeywordName[] = "to"; const char kTopKeywordName[] = "top";
diff --git a/src/cobalt/cssom/keyword_names.h b/src/cobalt/cssom/keyword_names.h index ccac4d5..86f0eda 100644 --- a/src/cobalt/cssom/keyword_names.h +++ b/src/cobalt/cssom/keyword_names.h
@@ -75,6 +75,7 @@ extern const char kLineThroughKeywordName[]; extern const char kMaroonKeywordName[]; extern const char kMiddleKeywordName[]; +extern const char kMonoscopicKeywordName[]; extern const char kMonospaceKeywordName[]; extern const char kNavyKeywordName[]; extern const char kNoneKeywordName[]; @@ -102,6 +103,8 @@ extern const char kStaticKeywordName[]; extern const char kStepEndKeywordName[]; extern const char kStepStartKeywordName[]; +extern const char kStereoscopicLeftRightKeywordName[]; +extern const char kStereoscopicTopBottomKeywordName[]; extern const char kTealKeywordName[]; extern const char kToKeywordName[]; extern const char kTopKeywordName[];
diff --git a/src/cobalt/cssom/keyword_value.cc b/src/cobalt/cssom/keyword_value.cc index 73aba27..0d90535 100644 --- a/src/cobalt/cssom/keyword_value.cc +++ b/src/cobalt/cssom/keyword_value.cc
@@ -56,6 +56,7 @@ left_value(new KeywordValue(KeywordValue::kLeft)), line_through_value(new KeywordValue(KeywordValue::kLineThrough)), middle_value(new KeywordValue(KeywordValue::kMiddle)), + monoscopic_value(new KeywordValue(KeywordValue::kMonoscopic)), monospace_value(new KeywordValue(KeywordValue::kMonospace)), none_value(new KeywordValue(KeywordValue::kNone)), no_repeat_value(new KeywordValue(KeywordValue::kNoRepeat)), @@ -73,6 +74,10 @@ solid_value(new KeywordValue(KeywordValue::kSolid)), start_value(new KeywordValue(KeywordValue::kStart)), static_value(new KeywordValue(KeywordValue::kStatic)), + stereoscopic_left_right_value( + new KeywordValue(KeywordValue::kStereoscopicLeftRight)), + stereoscopic_top_bottom_value( + new KeywordValue(KeywordValue::kStereoscopicTopBottom)), top_value(new KeywordValue(KeywordValue::kTop)), uppercase_value(new KeywordValue(KeywordValue::kUppercase)), visible_value(new KeywordValue(KeywordValue::kVisible)) {} @@ -107,6 +112,7 @@ const scoped_refptr<KeywordValue> left_value; const scoped_refptr<KeywordValue> line_through_value; const scoped_refptr<KeywordValue> middle_value; + const scoped_refptr<KeywordValue> monoscopic_value; const scoped_refptr<KeywordValue> monospace_value; const scoped_refptr<KeywordValue> none_value; const scoped_refptr<KeywordValue> no_repeat_value; @@ -124,6 +130,8 @@ const scoped_refptr<KeywordValue> solid_value; const scoped_refptr<KeywordValue> start_value; const scoped_refptr<KeywordValue> static_value; + const scoped_refptr<KeywordValue> stereoscopic_left_right_value; + const scoped_refptr<KeywordValue> stereoscopic_top_bottom_value; const scoped_refptr<KeywordValue> top_value; const scoped_refptr<KeywordValue> uppercase_value; const scoped_refptr<KeywordValue> visible_value; @@ -259,6 +267,10 @@ return non_trivial_static_fields.Get().middle_value; } +const scoped_refptr<KeywordValue>& KeywordValue::GetMonoscopic() { + return non_trivial_static_fields.Get().monoscopic_value; +} + const scoped_refptr<KeywordValue>& KeywordValue::GetMonospace() { return non_trivial_static_fields.Get().monospace_value; } @@ -327,6 +339,14 @@ return non_trivial_static_fields.Get().static_value; } +const scoped_refptr<KeywordValue>& KeywordValue::GetStereoscopicLeftRight() { + return non_trivial_static_fields.Get().stereoscopic_left_right_value; +} + +const scoped_refptr<KeywordValue>& KeywordValue::GetStereoscopicTopBottom() { + return non_trivial_static_fields.Get().stereoscopic_top_bottom_value; +} + const scoped_refptr<KeywordValue>& KeywordValue::GetTop() { return non_trivial_static_fields.Get().top_value; } @@ -407,6 +427,8 @@ return kMiddleKeywordName; case kMonospace: return kMonospaceKeywordName; + case kMonoscopic: + return kMonoscopicKeywordName; case kNone: return kNoneKeywordName; case kNoRepeat: @@ -439,6 +461,10 @@ return kStartKeywordName; case kStatic: return kStaticKeywordName; + case kStereoscopicLeftRight: + return kStereoscopicLeftRightKeywordName; + case kStereoscopicTopBottom: + return kStereoscopicTopBottomKeywordName; case kTop: return kTopKeywordName; case kUppercase:
diff --git a/src/cobalt/cssom/keyword_value.h b/src/cobalt/cssom/keyword_value.h index 2af8b00..bed4ba0 100644 --- a/src/cobalt/cssom/keyword_value.h +++ b/src/cobalt/cssom/keyword_value.h
@@ -202,6 +202,10 @@ // https://www.w3.org/TR/css3-fonts/#generic-font-families kMonospace, + // "monoscopic" is a value of the "cobalt-mtm" property which indicates + // that the mesh should only be rendered through one eye. + kMonoscopic, + // "none" is a value of "transform" property which means that HTML element // is rendered as is. // https://www.w3.org/TR/css3-transforms/#transform-property @@ -291,6 +295,15 @@ // https://www.w3.org/TR/CSS21/visuren.html#choose-position kStatic, + // "stereoscopic-left-right" is a value of the "cobalt-mtm" property which + // indicates that the mesh should be rendered in two views + // side-by-side. + kStereoscopicLeftRight, + + // "stereoscopic-top-bottom" is a value of the "cobalt-mtm" property which + // indicates that the mesh should be rendered in two views above and below. + kStereoscopicTopBottom, + // "top" is a value of "vertical-align" property that indicates that the // content should be aligned vertically at the top. // https://www.w3.org/TR/CSS21/visudet.html#propdef-vertical-align @@ -345,6 +358,7 @@ static const scoped_refptr<KeywordValue>& GetLeft(); static const scoped_refptr<KeywordValue>& GetLineThrough(); static const scoped_refptr<KeywordValue>& GetMiddle(); + static const scoped_refptr<KeywordValue>& GetMonoscopic(); static const scoped_refptr<KeywordValue>& GetMonospace(); static const scoped_refptr<KeywordValue>& GetNone(); static const scoped_refptr<KeywordValue>& GetNoRepeat(); @@ -362,6 +376,8 @@ static const scoped_refptr<KeywordValue>& GetSolid(); static const scoped_refptr<KeywordValue>& GetStart(); static const scoped_refptr<KeywordValue>& GetStatic(); + static const scoped_refptr<KeywordValue>& GetStereoscopicLeftRight(); + static const scoped_refptr<KeywordValue>& GetStereoscopicTopBottom(); static const scoped_refptr<KeywordValue>& GetTop(); static const scoped_refptr<KeywordValue>& GetUppercase(); static const scoped_refptr<KeywordValue>& GetVisible();
diff --git a/src/cobalt/cssom/mtm_function.cc b/src/cobalt/cssom/mtm_function.cc new file mode 100644 index 0000000..661060b --- /dev/null +++ b/src/cobalt/cssom/mtm_function.cc
@@ -0,0 +1,152 @@ +/* + * Copyright 2016 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/cssom/mtm_function.h" + +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" +#include "cobalt/cssom/filter_function_list_value.h" +#include "cobalt/cssom/keyword_value.h" +#include "cobalt/cssom/property_value_visitor.h" + +namespace cobalt { +namespace cssom { + +MTMFunction::MTMFunction( + const scoped_refptr<PropertyValue>& mesh_url, + ResolutionMatchedMeshListBuilder resolution_matched_meshes, + float horizontal_fov, float vertical_fov, const glm::mat4& transform, + const scoped_refptr<KeywordValue>& stereo_mode) + : mesh_url_(mesh_url), + resolution_matched_meshes_(resolution_matched_meshes.Pass()), + horizontal_fov_(horizontal_fov), + vertical_fov_(vertical_fov), + transform_(transform), + stereo_mode_(stereo_mode) { + DCHECK(mesh_url_); + DCHECK(stereo_mode_); +} + +std::string MTMFunction::ToString() const { + std::string result = "-cobalt-mtm("; + + result.append(mesh_url()->ToString()); + + const ResolutionMatchedMeshListBuilder& meshes = resolution_matched_meshes(); + for (size_t mesh_index = 0; mesh_index < meshes.size(); ++mesh_index) { + result.push_back(' '); + result.append(base::IntToString(meshes[mesh_index]->width_match())); + result.push_back(' '); + result.append(base::IntToString(meshes[mesh_index]->height_match())); + result.push_back(' '); + result.append(meshes[mesh_index]->mesh_url()->ToString()); + } + + result.append(base::StringPrintf(", %.7grad", horizontal_fov())); + result.append(base::StringPrintf(" %.7grad, ", vertical_fov())); + + result.append("matrix3d("); + for (int col = 0; col <= 3; ++col) { + for (int row = 0; row <= 3; ++row) { + if (col > 0 || row > 0) { + result.append(", "); + } + result.append(base::StringPrintf("%.7g", transform()[col][row])); + } + } + + result.append("), "); + result.append(stereo_mode()->ToString()); + result.append(")"); + + return result; +} + +MTMFunction::ResolutionMatchedMesh::ResolutionMatchedMesh( + int width_match, int height_match, + const scoped_refptr<PropertyValue>& mesh_url) + : width_match_(width_match), + height_match_(height_match), + mesh_url_(mesh_url) { + DCHECK(mesh_url_); +} + +bool MTMFunction::ResolutionMatchedMesh::operator==( + const MTMFunction::ResolutionMatchedMesh& rhs) const { + return mesh_url()->Equals(*rhs.mesh_url()) && + width_match() == rhs.width_match() && + height_match() == rhs.height_match(); +} + +bool MTMFunction::ResolutionMatchedMesh::operator!=( + const MTMFunction::ResolutionMatchedMesh& rhs) const { + return !(*this == rhs); +} + +const MTMFunction* MTMFunction::ExtractFromFilterList( + PropertyValue* filter_value) { + if (filter_value && filter_value != cssom::KeywordValue::GetNone()) { + const cssom::FilterFunctionListValue::Builder& filter_list = + base::polymorphic_downcast<cssom::FilterFunctionListValue*>( + filter_value) + ->value(); + + for (size_t list_index = 0; list_index < filter_list.size(); ++list_index) { + if (filter_list[list_index]->GetTypeId() == + base::GetTypeId<cssom::MTMFunction>()) { + return base::polymorphic_downcast<const cssom::MTMFunction*>( + filter_list[list_index]); + } + } + } + + return NULL; +} + +bool MTMFunction::operator==(const MTMFunction& rhs) const { + if (!mesh_url()->Equals(*rhs.mesh_url()) || + horizontal_fov() != rhs.horizontal_fov() || + horizontal_fov() != rhs.horizontal_fov() || + !stereo_mode()->Equals(*rhs.stereo_mode())) { + return false; + } + const ResolutionMatchedMeshListBuilder& lhs_meshes = + resolution_matched_meshes(); + const ResolutionMatchedMeshListBuilder& rhs_meshes = + rhs.resolution_matched_meshes(); + + if (lhs_meshes.size() != rhs_meshes.size()) { + return false; + } + + for (size_t i = 0; i < lhs_meshes.size(); ++i) { + if (*lhs_meshes[i] != *rhs_meshes[i]) { + return false; + } + } + + for (int col = 0; col <= 3; ++col) { + if (!glm::all(glm::equal(transform()[col], rhs.transform()[col]))) { + return false; + } + } + + return true; +} + +} // namespace cssom +} // namespace cobalt
diff --git a/src/cobalt/cssom/mtm_function.h b/src/cobalt/cssom/mtm_function.h new file mode 100644 index 0000000..764c0cb --- /dev/null +++ b/src/cobalt/cssom/mtm_function.h
@@ -0,0 +1,99 @@ +/* + * Copyright 2016 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_CSSOM_MTM_FUNCTION_H_ +#define COBALT_CSSOM_MTM_FUNCTION_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_vector.h" +#include "cobalt/base/polymorphic_equatable.h" +#include "cobalt/cssom/filter_function.h" +#include "cobalt/cssom/keyword_value.h" +#include "cobalt/cssom/property_value.h" +#include "cobalt/cssom/url_value.h" +#include "third_party/glm/glm/mat4x4.hpp" + +namespace cobalt { +namespace cssom { + +// Represent an MTM filter. +class MTMFunction : public FilterFunction { + public: + // A resolution-matched mesh specifier. + class ResolutionMatchedMesh { + public: + ResolutionMatchedMesh(int width_match, int height_match, + const scoped_refptr<PropertyValue>& mesh_url); + int width_match() const { return width_match_; } + int height_match() const { return height_match_; } + const scoped_refptr<PropertyValue>& mesh_url() const { return mesh_url_; } + bool operator==(const MTMFunction::ResolutionMatchedMesh& rhs) const; + bool operator!=(const MTMFunction::ResolutionMatchedMesh& rhs) const; + + private: + const int width_match_; + const int height_match_; + const scoped_refptr<PropertyValue> mesh_url_; + }; + typedef ScopedVector<ResolutionMatchedMesh> ResolutionMatchedMeshListBuilder; + + MTMFunction(const scoped_refptr<PropertyValue>& mesh_url, + ResolutionMatchedMeshListBuilder resolution_matched_meshes, + float horizontal_fov, float vertical_fov, + const glm::mat4& transform, + const scoped_refptr<KeywordValue>& stereo_mode); + + ~MTMFunction() OVERRIDE {} + + const scoped_refptr<PropertyValue>& mesh_url() const { return mesh_url_; } + const ResolutionMatchedMeshListBuilder& resolution_matched_meshes() const { + return resolution_matched_meshes_; + } + + float horizontal_fov() const { return horizontal_fov_; } + float vertical_fov() const { return vertical_fov_; } + const glm::mat4& transform() const { return transform_; } + const scoped_refptr<KeywordValue>& stereo_mode() const { + return stereo_mode_; + } + + std::string ToString() const OVERRIDE; + + bool operator==(const MTMFunction& rhs) const; + + static const MTMFunction* ExtractFromFilterList(PropertyValue* filter_list); + + DEFINE_POLYMORPHIC_EQUATABLE_TYPE(MTMFunction); + + private: + const scoped_refptr<PropertyValue> mesh_url_; + const ResolutionMatchedMeshListBuilder resolution_matched_meshes_; + const float horizontal_fov_; + const float vertical_fov_; + const glm::mat4 transform_; + const scoped_refptr<KeywordValue> stereo_mode_; + + DISALLOW_COPY_AND_ASSIGN(MTMFunction); +}; + +} // namespace cssom +} // namespace cobalt + +#endif // COBALT_CSSOM_MTM_FUNCTION_H_
diff --git a/src/cobalt/cssom/property_definitions.cc b/src/cobalt/cssom/property_definitions.cc index 8133992..1bcdb2a 100644 --- a/src/cobalt/cssom/property_definitions.cc +++ b/src/cobalt/cssom/property_definitions.cc
@@ -386,6 +386,12 @@ kImpactsBoxCrossReferencesNo, KeywordValue::GetInline()); + // https://www.w3.org/TR/filter-effects-1/#FilterProperty + SetPropertyDefinition(kFilterProperty, "filter", kInheritedNo, kAnimatableNo, + kImpactsChildDeclaredStyleNo, kImpactsBoxGenerationNo, + kImpactsBoxSizesNo, kImpactsBoxCrossReferencesNo, + KeywordValue::GetNone()); + // Varies by platform in Chromium, Roboto in Cobalt. // https://www.w3.org/TR/css3-fonts/#font-family-prop SetPropertyDefinition( @@ -1003,6 +1009,10 @@ return kBottomProperty; } if (LowerCaseEqualsASCII(property_name, + GetPropertyName(kFilterProperty))) { + return kFilterProperty; + } + if (LowerCaseEqualsASCII(property_name, GetPropertyName(kHeightProperty))) { return kHeightProperty; }
diff --git a/src/cobalt/cssom/property_definitions.h b/src/cobalt/cssom/property_definitions.h index 14a1c39..f4c9333 100644 --- a/src/cobalt/cssom/property_definitions.h +++ b/src/cobalt/cssom/property_definitions.h
@@ -72,6 +72,7 @@ kColorProperty, kContentProperty, kDisplayProperty, + kFilterProperty, kFontFamilyProperty, kFontSizeProperty, kFontStyleProperty,
diff --git a/src/cobalt/cssom/property_value_is_equal_test.cc b/src/cobalt/cssom/property_value_is_equal_test.cc index b2f4f88..263634b 100644 --- a/src/cobalt/cssom/property_value_is_equal_test.cc +++ b/src/cobalt/cssom/property_value_is_equal_test.cc
@@ -15,6 +15,7 @@ */ #include "cobalt/cssom/absolute_url_value.h" +#include "cobalt/cssom/filter_function_list_value.h" #include "cobalt/cssom/font_style_value.h" #include "cobalt/cssom/font_weight_value.h" #include "cobalt/cssom/integer_value.h" @@ -23,6 +24,7 @@ #include "cobalt/cssom/linear_gradient_value.h" #include "cobalt/cssom/matrix_function.h" #include "cobalt/cssom/media_feature_keyword_value.h" +#include "cobalt/cssom/mtm_function.h" #include "cobalt/cssom/number_value.h" #include "cobalt/cssom/percentage_value.h" #include "cobalt/cssom/property_definitions.h" @@ -596,5 +598,65 @@ EXPECT_FALSE(value_a->Equals(*value_b)); } +TEST(PropertyValueIsEqualTest, FilterListsAreEqual) { + FilterFunctionListValue::Builder filter_list_a; + filter_list_a.push_back(new MTMFunction( + new URLValue("somemesh.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 0.70707f, 6.28f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 9.0f, 0.0f, 0.0f, + 1.0f, 0.07878f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetMonoscopic())); + filter_list_a.push_back(new MTMFunction( + new URLValue("sphere.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 0.676f, 6.28f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetStereoscopicLeftRight())); + scoped_refptr<FilterFunctionListValue> value_a( + new FilterFunctionListValue(filter_list_a.Pass())); + + FilterFunctionListValue::Builder filter_list_b; + filter_list_b.push_back(new MTMFunction( + new URLValue("somemesh.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 0.70707f, 6.28f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 9.0f, 0.0f, 0.0f, + 1.0f, 0.07878f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetMonoscopic())); + filter_list_b.push_back(new MTMFunction( + new URLValue("sphere.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 0.676f, 6.28f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetStereoscopicLeftRight())); + scoped_refptr<FilterFunctionListValue> value_b( + new FilterFunctionListValue(filter_list_b.Pass())); + + EXPECT_TRUE(value_a->Equals(*value_b)); +} + +TEST(PropertyValueIsEqualTest, FilterListsAreNotEqual) { + FilterFunctionListValue::Builder filter_list_a; + filter_list_a.push_back(new MTMFunction( + new URLValue("format.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 3.14f, + glm::mat4(1.0f, 0.0f, 0.0f, 2.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetMonoscopic())); + scoped_refptr<FilterFunctionListValue> value_a( + new FilterFunctionListValue(filter_list_a.Pass())); + + FilterFunctionListValue::Builder filter_list_b; + filter_list_b.push_back(new MTMFunction( + new URLValue("format.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 3.14f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetMonoscopic())); + scoped_refptr<FilterFunctionListValue> value_b( + new FilterFunctionListValue(filter_list_b.Pass())); + + EXPECT_FALSE(value_a->Equals(*value_b)); +} + } // namespace cssom } // namespace cobalt
diff --git a/src/cobalt/cssom/property_value_to_string_test.cc b/src/cobalt/cssom/property_value_to_string_test.cc index 6c90ec6..7d18941 100644 --- a/src/cobalt/cssom/property_value_to_string_test.cc +++ b/src/cobalt/cssom/property_value_to_string_test.cc
@@ -18,6 +18,7 @@ #include "base/time.h" #include "cobalt/cssom/absolute_url_value.h" +#include "cobalt/cssom/filter_function_list_value.h" #include "cobalt/cssom/font_style_value.h" #include "cobalt/cssom/font_weight_value.h" #include "cobalt/cssom/integer_value.h" @@ -26,6 +27,7 @@ #include "cobalt/cssom/linear_gradient_value.h" #include "cobalt/cssom/matrix_function.h" #include "cobalt/cssom/media_feature_keyword_value.h" +#include "cobalt/cssom/mtm_function.h" #include "cobalt/cssom/number_value.h" #include "cobalt/cssom/percentage_value.h" #include "cobalt/cssom/property_definitions.h" @@ -52,9 +54,9 @@ namespace cssom { TEST(PropertyValueToStringTest, AbsoluteURLValue) { - GURL url("https://www.youtube.com"); + GURL url("https://www.test.com"); scoped_refptr<AbsoluteURLValue> property(new AbsoluteURLValue(url)); - EXPECT_EQ(property->ToString(), "url(https://www.youtube.com/)"); + EXPECT_EQ(property->ToString(), "url(https://www.test.com/)"); } TEST(PropertyValueToStringTest, FontStyleValue) { @@ -346,5 +348,89 @@ EXPECT_EQ(property->ToString(), "url(foo.png)"); } +TEST(PropertyValueToStringTest, MTMFunctionSingleMesh) { + scoped_ptr<MTMFunction> function(new MTMFunction( + new URLValue("-.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 2.5f, 3.14f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetMonoscopic())); + EXPECT_EQ( + "-cobalt-mtm(url(-.msh), " + "2.5rad 3.14rad, " + "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), " + "monoscopic)", + function->ToString()); +} + +TEST(PropertyValueToStringTest, MTMFunctionWithResolutionMatchedMeshes) { + MTMFunction::ResolutionMatchedMeshListBuilder meshes; + meshes.push_back(new MTMFunction::ResolutionMatchedMesh( + 1920, 2000000, new URLValue("a.msh"))); + meshes.push_back( + new MTMFunction::ResolutionMatchedMesh(640, 5, new URLValue("b.msh"))); + scoped_ptr<MTMFunction> function( + new MTMFunction(new URLValue("-.msh"), meshes.Pass(), 28.5f, 3.14f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetStereoscopicLeftRight())); + EXPECT_EQ( + "-cobalt-mtm(url(-.msh) 1920 2000000 url(a.msh) 640 5 url(b.msh), " + "28.5rad 3.14rad, " + "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), " + "stereoscopic-left-right)", + function->ToString()); +} + +TEST(PropertyValueToStringTest, FilterFunctionListValue) { + FilterFunctionListValue::Builder filter_list; + filter_list.push_back(new MTMFunction( + new URLValue("-.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 3.14f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetMonoscopic())); + filter_list.push_back(new MTMFunction( + new URLValue("world.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 39.0f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetMonoscopic())); + filter_list.push_back(new MTMFunction( + new URLValue("stereoscopic-world.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 39.0f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetStereoscopicLeftRight())); + filter_list.push_back(new MTMFunction( + new URLValue("stereoscopic-top-bottom-world.msh"), + MTMFunction::ResolutionMatchedMeshListBuilder().Pass(), 8.5f, 39.0f, + glm::mat4(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f), + KeywordValue::GetStereoscopicTopBottom())); + + scoped_refptr<FilterFunctionListValue> property( + new FilterFunctionListValue(filter_list.Pass())); + + EXPECT_EQ( + "-cobalt-mtm(url(-.msh), " + "8.5rad 3.14rad, " + "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), " + "monoscopic) " + "-cobalt-mtm(url(world.msh), " + "8.5rad 39rad, " + "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), " + "monoscopic) " + "-cobalt-mtm(url(stereoscopic-world.msh), " + "8.5rad 39rad, " + "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), " + "stereoscopic-left-right) " + "-cobalt-mtm(url(stereoscopic-top-bottom-world.msh), " + "8.5rad 39rad, " + "matrix3d(1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), " + "stereoscopic-top-bottom)", + property->ToString()); +} + } // namespace cssom } // namespace cobalt
diff --git a/src/cobalt/cssom/property_value_visitor.cc b/src/cobalt/cssom/property_value_visitor.cc index cba67cb..12d234b 100644 --- a/src/cobalt/cssom/property_value_visitor.cc +++ b/src/cobalt/cssom/property_value_visitor.cc
@@ -19,6 +19,7 @@ #include "base/logging.h" #include "cobalt/cssom/absolute_url_value.h" #include "cobalt/cssom/calc_value.h" +#include "cobalt/cssom/filter_function_list_value.h" #include "cobalt/cssom/font_style_value.h" #include "cobalt/cssom/font_weight_value.h" #include "cobalt/cssom/integer_value.h" @@ -57,6 +58,11 @@ VisitDefault(calc_value); } +void DefaultingPropertyValueVisitor::VisitFilterFunctionList( + FilterFunctionListValue* filter_function_list_value) { + VisitDefault(filter_function_list_value); +} + void DefaultingPropertyValueVisitor::VisitFontStyle( FontStyleValue* font_style_value) { VisitDefault(font_style_value);
diff --git a/src/cobalt/cssom/property_value_visitor.h b/src/cobalt/cssom/property_value_visitor.h index d363630..3457a9f 100644 --- a/src/cobalt/cssom/property_value_visitor.h +++ b/src/cobalt/cssom/property_value_visitor.h
@@ -24,6 +24,7 @@ class AbsoluteURLValue; class CalcValue; +class FilterFunctionListValue; class FontStyleValue; class FontWeightValue; class IntegerValue; @@ -58,6 +59,7 @@ public: virtual void VisitAbsoluteURL(AbsoluteURLValue* url_value) = 0; virtual void VisitCalc(CalcValue* calc_value) = 0; + virtual void VisitFilterFunctionList(FilterFunctionListValue*) = 0; virtual void VisitFontStyle(FontStyleValue* font_style_value) = 0; virtual void VisitFontWeight(FontWeightValue* font_weight_value) = 0; virtual void VisitInteger(IntegerValue* integer_value) = 0; @@ -102,6 +104,8 @@ public: void VisitAbsoluteURL(AbsoluteURLValue* url_value) OVERRIDE; void VisitCalc(CalcValue* calc_value) OVERRIDE; + void VisitFilterFunctionList( + FilterFunctionListValue* filter_function_list_value) OVERRIDE; void VisitFontStyle(FontStyleValue* font_style_value) OVERRIDE; void VisitFontWeight(FontWeightValue* font_weight_value) OVERRIDE; void VisitKeyword(KeywordValue* keyword_value) OVERRIDE;
diff --git a/src/cobalt/cssom/property_value_visitor_test.cc b/src/cobalt/cssom/property_value_visitor_test.cc index 953dd12..1ff138d 100644 --- a/src/cobalt/cssom/property_value_visitor_test.cc +++ b/src/cobalt/cssom/property_value_visitor_test.cc
@@ -20,6 +20,7 @@ #include "cobalt/cssom/absolute_url_value.h" #include "cobalt/cssom/calc_value.h" +#include "cobalt/cssom/filter_function_list_value.h" #include "cobalt/cssom/font_style_value.h" #include "cobalt/cssom/font_weight_value.h" #include "cobalt/cssom/integer_value.h" @@ -28,6 +29,7 @@ #include "cobalt/cssom/linear_gradient_value.h" #include "cobalt/cssom/local_src_value.h" #include "cobalt/cssom/media_feature_keyword_value.h" +#include "cobalt/cssom/mtm_function.h" #include "cobalt/cssom/number_value.h" #include "cobalt/cssom/percentage_value.h" #include "cobalt/cssom/property_key_list_value.h" @@ -59,6 +61,8 @@ MOCK_METHOD1(VisitAbsoluteURL, void(AbsoluteURLValue* absolute_url_value)); MOCK_METHOD1(VisitCalc, void(CalcValue* calc_value)); MOCK_METHOD1(VisitFontStyle, void(FontStyleValue* font_style_value)); + MOCK_METHOD1(VisitFilterFunctionList, + void(FilterFunctionListValue* filter_list_value)); MOCK_METHOD1(VisitFontWeight, void(FontWeightValue* font_weight_value)); MOCK_METHOD1(VisitInteger, void(IntegerValue* integer_value)); MOCK_METHOD1(VisitKeyword, void(KeywordValue* keyword_value)); @@ -110,6 +114,24 @@ calc_value->Accept(&mock_visitor); } +TEST(PropertyValueVisitorTest, VisitsFilterListValue) { + MTMFunction::ResolutionMatchedMeshListBuilder resMs; + resMs.push_back( + new MTMFunction::ResolutionMatchedMesh(22, 22, new URLValue("a.msh"))); + + FilterFunctionListValue::Builder builder; + builder.push_back(new MTMFunction(new URLValue("p.msh"), resMs.Pass(), 120, + 60, glm::mat4(1.0f), + KeywordValue::GetMonoscopic())); + + scoped_refptr<FilterFunctionListValue> filter_list_value = + new FilterFunctionListValue(builder.Pass()); + + MockPropertyValueVisitor mock_visitor; + EXPECT_CALL(mock_visitor, VisitFilterFunctionList(filter_list_value.get())); + filter_list_value->Accept(&mock_visitor); +} + TEST(PropertyValueVisitorTest, VisitsFontStyleValue) { scoped_refptr<FontStyleValue> font_style_value = FontStyleValue::GetNormal(); MockPropertyValueVisitor mock_visitor;
diff --git a/src/cobalt/cssom/selector_test.cc b/src/cobalt/cssom/selector_test.cc index 090d9d7..c120db8 100644 --- a/src/cobalt/cssom/selector_test.cc +++ b/src/cobalt/cssom/selector_test.cc
@@ -115,5 +115,42 @@ EXPECT_EQ(Specificity(1, 2, 3), complex_selector->GetSpecificity()); } +TEST(SelectorTest, ComplexSelectorAppendCallLimit) { + { + scoped_ptr<ComplexSelector> complex_selector(new ComplexSelector()); + complex_selector->AppendSelector( + make_scoped_ptr<CompoundSelector>(new CompoundSelector())); + + for (int i = 0; i < ComplexSelector::kCombinatorLimit; + i++) { + scoped_ptr<CompoundSelector> compound_selector(new CompoundSelector()); + scoped_ptr<ChildCombinator> child_combinator(new ChildCombinator()); + complex_selector->AppendCombinatorAndSelector( + child_combinator.PassAs<Combinator>(), compound_selector.Pass()); + } + + EXPECT_EQ(complex_selector->combinator_count(), + ComplexSelector::kCombinatorLimit); + } + + { + scoped_ptr<ComplexSelector> complex_selector(new ComplexSelector()); + complex_selector->AppendSelector( + make_scoped_ptr<CompoundSelector>(new CompoundSelector())); + + for (int i = 0; + i < 2 * ComplexSelector::kCombinatorLimit + 1; + i++) { + scoped_ptr<CompoundSelector> compound_selector(new CompoundSelector()); + scoped_ptr<ChildCombinator> child_combinator(new ChildCombinator()); + complex_selector->AppendCombinatorAndSelector( + child_combinator.PassAs<Combinator>(), compound_selector.Pass()); + } + + EXPECT_EQ(complex_selector->combinator_count(), + ComplexSelector::kCombinatorLimit); + } +} + } // namespace cssom } // namespace cobalt
diff --git a/src/cobalt/dom/Blob.idl b/src/cobalt/dom/Blob.idl index aa0fcb6..edb972d 100644 --- a/src/cobalt/dom/Blob.idl +++ b/src/cobalt/dom/Blob.idl
@@ -14,7 +14,16 @@ * limitations under the License. */ -// https://www.w3.org/TR/FileAPI/#dfn-Blob +// https://www.w3.org/TR/2015/WD-FileAPI-20150421/#dfn-Blob -// Stub implementation of Blob so we can call "instanceof Blob". -interface Blob {}; +[ + //Constructor, + // TODO: When arrays or sequences are supported, support the constructor + // defined in the standard with multiple blobparts, and drop the single + // ArrayBuffer constructor. + Constructor(optional ArrayBuffer buffer), + ConstructorCallWith=EnvironmentSettings +] +interface Blob { + readonly attribute unsigned long long size; +};
diff --git a/src/cobalt/dom/Int16Array.idl b/src/cobalt/dom/Int16Array.idl new file mode 100644 index 0000000..3cc4d3c --- /dev/null +++ b/src/cobalt/dom/Int16Array.idl
@@ -0,0 +1,42 @@ +/* + * Copyright 2016 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. + */ + +// https://www.khronos.org/registry/typedarray/specs/latest/#7 +[ + Constructor(unsigned long length), + Constructor(Int16Array array), + Constructor(ArrayBuffer buffer, optional unsigned long byteOffset, optional unsigned long length), + ConstructorCallWith=EnvironmentSettings, + RaisesException=Constructor +] +interface Int16Array : ArrayBufferView { + const unsigned long BYTES_PER_ELEMENT = 2; + + readonly attribute unsigned long length; + + getter short get(unsigned long index); + setter void set(unsigned long index, short value); + + // TODO: When arrays or sequences are supported, support them here. + // void set(sequence<long> array, optional unsigned long offset); + + // Copy items into this array from source, starting at this[offset]. + [RaisesException] void set(Int16Array source, optional unsigned long offset); + + // Return a new Int16Array that is a view on top of this one. + // Contains this[start]..this[end]. end defaults to length if unspecified. + [CallWith=EnvironmentSettings] Int16Array subarray(long start, optional long end); +};
diff --git a/src/cobalt/dom/MediaSource.idl b/src/cobalt/dom/MediaSource.idl index a53419c..65eeb35 100644 --- a/src/cobalt/dom/MediaSource.idl +++ b/src/cobalt/dom/MediaSource.idl
@@ -28,4 +28,6 @@ readonly attribute DOMString readyState; [RaisesException] void endOfStream(optional DOMString error); + + [CallWith=EnvironmentSettings] static boolean isTypeSupported(DOMString type); };
diff --git a/src/cobalt/dom/URL.idl b/src/cobalt/dom/URL.idl index 0273f57..8784d1d 100644 --- a/src/cobalt/dom/URL.idl +++ b/src/cobalt/dom/URL.idl
@@ -19,5 +19,7 @@ interface URL { [CallWith=EnvironmentSettings] static DOMString createObjectURL(MediaSource mediaSource); + [CallWith=EnvironmentSettings] static DOMString + createObjectURL(Blob blob); [CallWith=EnvironmentSettings] static void revokeObjectURL(DOMString url); };
diff --git a/src/cobalt/dom/blob.h b/src/cobalt/dom/blob.h index 8f11422..d677e04 100644 --- a/src/cobalt/dom/blob.h +++ b/src/cobalt/dom/blob.h
@@ -17,15 +17,45 @@ #ifndef COBALT_DOM_BLOB_H_ #define COBALT_DOM_BLOB_H_ +#include <vector> + +#include "cobalt/dom/array_buffer.h" +#include "cobalt/dom/url_registry.h" +#include "cobalt/script/environment_settings.h" #include "cobalt/script/wrappable.h" +#include "googleurl/src/gurl.h" namespace cobalt { namespace dom { -// Stub implementation of Blob so we can call "instanceof Blob". +// A Blob object refers to a byte sequence, and has a size attribute which is +// the total number of bytes in the byte sequence, and a type attribute, which +// is an ASCII-encoded string in lower case representing the media type of the +// byte sequence. +// https://www.w3.org/TR/2015/WD-FileAPI-20150421/#dfn-Blob +// +// Note: Cobalt currently does not implement nor need the type attribute. class Blob : public script::Wrappable { public: + typedef UrlRegistry<Blob> Registry; + + Blob(script::EnvironmentSettings* settings, + const scoped_refptr<ArrayBuffer>& buffer = NULL) + : buffer_( + buffer ? buffer->Slice(settings, 0) + : scoped_refptr<ArrayBuffer>(new ArrayBuffer(settings, 0))) { + } + + const uint8* data() { return buffer_->data(); } + + uint64 size() { return static_cast<uint64>(buffer_->byte_length()); } + DEFINE_WRAPPABLE_TYPE(Blob); + + private: + scoped_refptr<ArrayBuffer> buffer_; + + DISALLOW_COPY_AND_ASSIGN(Blob); }; } // namespace dom
diff --git a/src/cobalt/dom/blob_test.cc b/src/cobalt/dom/blob_test.cc new file mode 100644 index 0000000..5d252da --- /dev/null +++ b/src/cobalt/dom/blob_test.cc
@@ -0,0 +1,80 @@ +/* + * Copyright 2016 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 <algorithm> + +#include "cobalt/dom/blob.h" +#include "cobalt/dom/data_view.h" +#include "cobalt/script/testing/mock_exception_state.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cobalt { +namespace dom { +namespace { + +using script::testing::MockExceptionState; +using testing::_; +using testing::SaveArg; +using testing::StrictMock; + +TEST(BlobTest, Constructors) { + scoped_refptr<Blob> blob_default_buffer = new Blob(NULL); + + EXPECT_EQ(0, blob_default_buffer->size()); + + StrictMock<MockExceptionState> exception_state; + scoped_refptr<ArrayBuffer> array_buffer = new ArrayBuffer(NULL, 5); + scoped_refptr<DataView> data_view = + new DataView(array_buffer, &exception_state); + data_view->SetInt16(0, static_cast<int16>(0x0607), &exception_state); + + scoped_refptr<Blob> blob_with_buffer = new Blob(NULL, array_buffer); + + ASSERT_EQ(5, blob_with_buffer->size()); + ASSERT_TRUE(blob_with_buffer->data()); + + EXPECT_EQ(0x6, blob_with_buffer->data()[0]); + EXPECT_EQ(0x7, blob_with_buffer->data()[1]); + EXPECT_EQ(0, blob_with_buffer->data()[2]); + EXPECT_EQ(0, blob_with_buffer->data()[3]); + EXPECT_EQ(0, blob_with_buffer->data()[4]); +} + +// Tests that further changes to a buffer from which a blob was constructed +// no longer affect the blob's buffer, since it must be a separate copy of +// its construction arguments. +TEST(BlobTest, HasOwnBuffer) { + StrictMock<MockExceptionState> exception_state; + scoped_refptr<ArrayBuffer> array_buffer = new ArrayBuffer(NULL, 2); + scoped_refptr<DataView> data_view = + new DataView(array_buffer, &exception_state); + data_view->SetInt16(0, static_cast<int16>(0x0607), &exception_state); + + scoped_refptr<Blob> blob_with_buffer = new Blob(NULL, array_buffer); + + ASSERT_EQ(2, blob_with_buffer->size()); + ASSERT_TRUE(blob_with_buffer->data()); + EXPECT_NE(array_buffer->data(), blob_with_buffer->data()); + + data_view->SetUint8(1, static_cast<uint8>(0xff), &exception_state); + + EXPECT_EQ(0x6, blob_with_buffer->data()[0]); + EXPECT_EQ(0x7, blob_with_buffer->data()[1]); +} + +} // namespace +} // namespace dom +} // namespace cobalt
diff --git a/src/cobalt/dom/dom.gyp b/src/cobalt/dom/dom.gyp index 6087406..71ab518 100644 --- a/src/cobalt/dom/dom.gyp +++ b/src/cobalt/dom/dom.gyp
@@ -234,6 +234,7 @@ 'uint8_array.h', 'url.cc', 'url.h', + 'url_registry.h', 'url_utils.cc', 'url_utils.h', 'window.cc', @@ -257,6 +258,7 @@ '<(DEPTH)/cobalt/storage/storage.gyp:storage', '<(DEPTH)/cobalt/web_animations/web_animations.gyp:web_animations', '<(DEPTH)/googleurl/googleurl.gyp:googleurl', + '<(DEPTH)/nb/nb.gyp:nb', ], },
diff --git a/src/cobalt/dom/dom_settings.cc b/src/cobalt/dom/dom_settings.cc index 6588e93..e0bb25a 100644 --- a/src/cobalt/dom/dom_settings.cc +++ b/src/cobalt/dom/dom_settings.cc
@@ -21,21 +21,23 @@ namespace cobalt { namespace dom { -DOMSettings::DOMSettings(const int max_dom_element_depth, - loader::FetcherFactory* fetcher_factory, - network::NetworkModule* network_module, - const scoped_refptr<Window>& window, - MediaSource::Registry* media_source_registry, - script::JavaScriptEngine* engine, - script::GlobalEnvironment* global_environment, - const Options& options) +DOMSettings::DOMSettings( + const int max_dom_element_depth, loader::FetcherFactory* fetcher_factory, + network::NetworkModule* network_module, const scoped_refptr<Window>& window, + MediaSource::Registry* media_source_registry, Blob::Registry* blob_registry, + media::CanPlayTypeHandler* can_play_type_handler, + script::JavaScriptEngine* engine, + script::GlobalEnvironment* global_environment, const Options& options) : max_dom_element_depth_(max_dom_element_depth), + microphone_options_(options.microphone_options), fetcher_factory_(fetcher_factory), network_module_(network_module), window_(window), array_buffer_allocator_(options.array_buffer_allocator), array_buffer_cache_(options.array_buffer_cache), media_source_registry_(media_source_registry), + blob_registry_(blob_registry), + can_play_type_handler_(can_play_type_handler), javascript_engine_(engine), global_environment_(global_environment) { if (array_buffer_allocator_) {
diff --git a/src/cobalt/dom/dom_settings.h b/src/cobalt/dom/dom_settings.h index 8366e67..e2099c7 100644 --- a/src/cobalt/dom/dom_settings.h +++ b/src/cobalt/dom/dom_settings.h
@@ -20,9 +20,12 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "cobalt/dom/array_buffer.h" +#include "cobalt/dom/blob.h" #include "cobalt/dom/media_source.h" #include "cobalt/dom/window.h" +#include "cobalt/media/can_play_type_handler.h" #include "cobalt/script/environment_settings.h" +#include "cobalt/speech/microphone.h" namespace cobalt { @@ -55,6 +58,8 @@ // amount of ArrayBuffer inside main memory. So we have provide the // following cache to manage ArrayBuffer in main memory. ArrayBuffer::Cache* array_buffer_cache; + // Microphone options. + speech::Microphone::Options microphone_options; }; DOMSettings(const int max_dom_element_depth, @@ -62,12 +67,17 @@ network::NetworkModule* network_module, const scoped_refptr<Window>& window, MediaSource::Registry* media_source_registry, + Blob::Registry* blob_registry, + media::CanPlayTypeHandler* can_play_type_handler, script::JavaScriptEngine* engine, script::GlobalEnvironment* global_environment_proxy, const Options& options = Options()); ~DOMSettings() OVERRIDE; int max_dom_element_depth() { return max_dom_element_depth_; } + const speech::Microphone::Options& microphone_options() const { + return microphone_options_; + } void set_window(const scoped_refptr<Window>& window) { window_ = window; } scoped_refptr<Window> window() const { return window_; } @@ -95,18 +105,25 @@ MediaSource::Registry* media_source_registry() const { return media_source_registry_; } + media::CanPlayTypeHandler* can_play_type_handler() const { + return can_play_type_handler_; + } + Blob::Registry* blob_registry() const { return blob_registry_; } // An absolute URL used to resolve relative URLs. virtual GURL base_url() const; private: const int max_dom_element_depth_; + const speech::Microphone::Options microphone_options_; loader::FetcherFactory* fetcher_factory_; network::NetworkModule* network_module_; scoped_refptr<Window> window_; ArrayBuffer::Allocator* array_buffer_allocator_; ArrayBuffer::Cache* array_buffer_cache_; MediaSource::Registry* media_source_registry_; + Blob::Registry* blob_registry_; + media::CanPlayTypeHandler* can_play_type_handler_; script::JavaScriptEngine* javascript_engine_; script::GlobalEnvironment* global_environment_;
diff --git a/src/cobalt/dom/dom_test.gyp b/src/cobalt/dom/dom_test.gyp index a399a4e..b96ce65 100644 --- a/src/cobalt/dom/dom_test.gyp +++ b/src/cobalt/dom/dom_test.gyp
@@ -24,6 +24,7 @@ 'target_name': 'dom_test', 'type': '<(gtest_target_type)', 'sources': [ + 'blob_test.cc', 'comment_test.cc', 'crypto_test.cc', 'csp_delegate_test.cc', @@ -41,6 +42,7 @@ 'event_test.cc', 'float32_array_test.cc', 'float64_array_test.cc', + 'font_cache_test.cc', 'html_element_factory_test.cc', 'html_element_test.cc', 'keyboard_event_test.cc', @@ -70,6 +72,7 @@ '<(DEPTH)/cobalt/dom/dom.gyp:dom', '<(DEPTH)/cobalt/dom/dom.gyp:dom_testing', '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser', + '<(DEPTH)/cobalt/renderer/rasterizer/skia/skia/skia.gyp:skia', '<(DEPTH)/testing/gmock.gyp:gmock', '<(DEPTH)/testing/gtest.gyp:gtest', ],
diff --git a/src/cobalt/dom/event_listener.cc b/src/cobalt/dom/event_listener.cc index 45da194..4d9959e 100644 --- a/src/cobalt/dom/event_listener.cc +++ b/src/cobalt/dom/event_listener.cc
@@ -59,8 +59,9 @@ snprintf(event_log_stack_[current_stack_depth_], kLogEntryMaxLength, "%s@%s", event->type().c_str(), event->current_target()->GetDebugName().c_str()); - } else { - NOTREACHED(); + } else if (current_stack_depth_ == base::UserLog::kEventStackMaxDepth) { + DLOG(WARNING) << "Reached maximum depth of " << kLogEntryMaxLength + << ". Subsequent events will not be logged."; } current_stack_depth_++; } @@ -68,7 +69,9 @@ void PopEvent() { DCHECK(current_stack_depth_); current_stack_depth_--; - memset(event_log_stack_[current_stack_depth_], 0, kLogEntryMaxLength); + if (current_stack_depth_ < base::UserLog::kEventStackMaxDepth) { + memset(event_log_stack_[current_stack_depth_], 0, kLogEntryMaxLength); + } } private:
diff --git a/src/cobalt/dom/event_target.cc b/src/cobalt/dom/event_target.cc index 87553ff..39175e3 100644 --- a/src/cobalt/dom/event_target.cc +++ b/src/cobalt/dom/event_target.cc
@@ -107,6 +107,9 @@ void EventTarget::PostToDispatchEventAndRunCallback( const tracked_objects::Location& location, base::Token event_name, const base::Closure& callback) { + if (!MessageLoop::current()) { + return; + } MessageLoop::current()->PostTask( location, base::Bind(base::IgnoreResult(&EventTarget::DispatchEventAndRunCallback),
diff --git a/src/cobalt/dom/event_target.h b/src/cobalt/dom/event_target.h index 462d7a6..3fc4df9 100644 --- a/src/cobalt/dom/event_target.h +++ b/src/cobalt/dom/event_target.h
@@ -68,12 +68,14 @@ void DispatchEventAndRunCallback(base::Token event_name, const base::Closure& dispatched_callback); - // Posts a task on the current message loop to dispatch event. + // Posts a task on the current message loop to dispatch event. It does nothing + // if there is no current message loop. void PostToDispatchEvent(const tracked_objects::Location& location, base::Token event_name); // Posts a task on the current message loop to dispatch event, and runs - // dispatched_callback after finish. + // dispatched_callback after finish. It does nothing if there is no current + // message loop. void PostToDispatchEventAndRunCallback( const tracked_objects::Location& location, base::Token event_name, const base::Closure& dispatched_callback);
diff --git a/src/cobalt/dom/font_cache.cc b/src/cobalt/dom/font_cache.cc index 0ee045c..9c9750e 100644 --- a/src/cobalt/dom/font_cache.cc +++ b/src/cobalt/dom/font_cache.cc
@@ -155,7 +155,7 @@ return TryGetRemoteFont(source_iterator->GetUrl(), size, state); } else { scoped_refptr<render_tree::Font> font = - TryGetLocalFont(source_iterator->GetName(), style, size, state); + TryGetLocalFontByFaceName(source_iterator->GetName(), size, state); if (font != NULL) { return font; } @@ -283,6 +283,7 @@ const scoped_refptr<render_tree::Typeface>& FontCache::GetCachedLocalTypeface( const scoped_refptr<render_tree::Typeface>& typeface) { + DCHECK(typeface); // Check to see if a typeface with a matching id is already in the cache. If // it is not, then add the passed in typeface to the cache. scoped_refptr<render_tree::Typeface>& cached_typeface = @@ -308,6 +309,7 @@ // completed or the timer expires. if (requested_remote_typeface_iterator == requested_remote_typeface_cache_.end()) { + DLOG(INFO) << "Requested remote font from " << url; // Create the remote typeface load event's callback. This callback occurs on // successful loads, failed loads, and when the request's timer expires. base::Closure typeface_load_event_callback = base::Bind( @@ -317,7 +319,7 @@ // the iterator from the return value of the map insertion. requested_remote_typeface_iterator = requested_remote_typeface_cache_ - .insert(std::make_pair( + .insert(RequestedRemoteTypefaceMap::value_type( url, new RequestedRemoteTypefaceInfo( cached_remote_typeface, typeface_load_event_callback))) .first; @@ -346,6 +348,7 @@ const std::string& family, render_tree::FontStyle style, float size, FontListFont::State* state) { DCHECK(resource_provider()); + DCHECK(resource_provider() != NULL); // Only request the local font from the resource provider if the family is // empty or the resource provider actually has the family. The reason for this // is that the resource provider's |GetLocalTypeface()| is guaranteed to @@ -365,6 +368,28 @@ } } +scoped_refptr<render_tree::Font> FontCache::TryGetLocalFontByFaceName( + const std::string& font_face, float size, FontListFont::State* state) { + do { + if (font_face.empty()) { + break; + } + const scoped_refptr<render_tree::Typeface>& typeface( + resource_provider()->GetLocalTypefaceByFaceNameIfAvailable(font_face)); + if (!typeface) { + break; + } + const scoped_refptr<render_tree::Typeface>& typeface_cached( + GetCachedLocalTypeface(typeface)); + + *state = FontListFont::kLoadedState; + return GetFontFromTypefaceAndSize(typeface_cached, size); + } while (false); + + *state = FontListFont::kUnavailableState; + return NULL; +} + void FontCache::OnRemoteTypefaceLoadEvent(const GURL& url) { DCHECK(thread_checker_.CalledOnValidThread()); RequestedRemoteTypefaceMap::iterator requested_remote_typeface_iterator =
diff --git a/src/cobalt/dom/font_cache.h b/src/cobalt/dom/font_cache.h index 89566cf..8e5afc5 100644 --- a/src/cobalt/dom/font_cache.h +++ b/src/cobalt/dom/font_cache.h
@@ -39,20 +39,21 @@ namespace dom { // The font cache is typically owned by dom::Document and handles the following: +// - Tracking of font faces, which it uses to determine if a specified +// font family is local or remote, and for url determination in requesting +// remote typefaces. // - Creation and caching of font lists, which it provides to the used // style provider as requested. Font lists handle most layout-related font // cache interactions. Layout objects only interact with the font cache // through their font lists. -// - Tracking of font faces, which it uses to determine if a specified -// font family is local or remote, and for url determination in requesting -// remote typefaces. // - Retrieval of typefaces, either locally from the resource provider or -// remotely from the remote typeface cache. -// - Caching the indices of the glyphs that the typeface provides for specific -// characters, so that only the first query of a specific font character -// necessitates the glyph lookup. -// - Determination of the fallback typeface for a specific character, and -// caching of that information for subsequent lookups. +// remotely from the remote typeface cache, and caching of both typefaces +// and fonts to facilitate sharing of them across font lists. +// - Determination of the fallback typeface for a specific character using a +// specific font style, and caching of that information for subsequent +// lookups. +// - Creation of glyph buffers, which is accomplished by passing the request +// to the resource provider. // NOTE: The font cache is not thread-safe and must only used within a single // thread. class FontCache { @@ -253,19 +254,22 @@ // the constructor and an |OnRemoteFontLoadEvent| callback provided by the // font are registered with the remote typeface cache to be called when the // load finishes. - // If the font is loading but not currently available, |maybe_is_font_loading| - // will be set to true. scoped_refptr<render_tree::Font> TryGetRemoteFont(const GURL& url, float size, FontListFont::State* state); // Returns NULL if the requested family is not empty and is not available in // the resource provider. Otherwise, returns the best matching local font. - // |maybe_is_font_loading| is always set to false. scoped_refptr<render_tree::Font> TryGetLocalFont(const std::string& family, render_tree::FontStyle style, float size, FontListFont::State* state); + // Lookup by a typeface (aka font_face), typeface is defined as font family + + // style (weight, width, and style). + // Returns NULL if the requested font face is not found. + scoped_refptr<render_tree::Font> TryGetLocalFontByFaceName( + const std::string& font_face, float size, FontListFont::State* state); + // Called when a remote typeface either successfully loads or fails to load. // In either case, the event can impact the fonts contained within the font // lists. As a result, the font lists need to have their loading fonts reset
diff --git a/src/cobalt/dom/font_cache_test.cc b/src/cobalt/dom/font_cache_test.cc new file mode 100644 index 0000000..a46b631 --- /dev/null +++ b/src/cobalt/dom/font_cache_test.cc
@@ -0,0 +1,134 @@ +/* + * Copyright 2016 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/dom/font_cache.h" + +#include "base/bind.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "cobalt/csp/content_security_policy.h" +#include "cobalt/dom/font_face.h" +#include "cobalt/loader/font/remote_typeface_cache.h" +#include "cobalt/loader/font/typeface_decoder.h" +#include "cobalt/loader/loader.h" +#include "cobalt/loader/mock_loader_factory.h" +#include "cobalt/render_tree/mock_resource_provider.h" +#include "cobalt/render_tree/resource_provider.h" +#include "cobalt/render_tree/resource_provider_stub.h" +#include "googleurl/src/gurl.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cobalt { +namespace dom { + +using ::testing::_; +using ::testing::Return; + +const render_tree::FontStyle kNormalUpright( + render_tree::FontStyle::kNormalWeight, + render_tree::FontStyle::kUprightSlant); + +scoped_ptr<FontCache::FontFaceMap> CreateFontFaceMapHelper( + const std::string& family_name, const base::StringPiece local_font_name) { + scoped_ptr<FontCache::FontFaceMap> ffm(new FontCache::FontFaceMap()); + dom::FontFaceStyleSet ffss; + dom::FontFaceStyleSet::Entry entry; + entry.sources.push_back( + FontFaceSource(local_font_name.as_string())); // local() + entry.sources.push_back(FontFaceSource( + GURL("https://example.com/Dancing-Regular.woff"))); // url() + ffss.AddEntry(entry); + ffm->insert(FontCache::FontFaceMap::value_type(family_name, ffss)); + return ffm.Pass(); +} + +class FontCacheTest : public ::testing::Test { + public: + FontCacheTest(); + ~FontCacheTest() OVERRIDE {} + + void DummyOnTypefaceLoadEvent() {} + + protected: + ::testing::StrictMock<loader::MockLoaderFactory> loader_factory_; + scoped_refptr<cobalt::render_tree::Typeface> sample_typeface_; + ::testing::StrictMock<cobalt::render_tree::MockResourceProvider> + mock_resource_provider_; + cobalt::render_tree::ResourceProvider* mrp; + scoped_ptr<loader::font::RemoteTypefaceCache> rtc; + scoped_ptr<dom::FontCache> font_cache_; + + MessageLoop message_loop_; +}; + +FontCacheTest::FontCacheTest() + : sample_typeface_(new render_tree::TypefaceStub(NULL)), + mrp(dynamic_cast<cobalt::render_tree::ResourceProvider*>( + &mock_resource_provider_)), + rtc(new loader::font::RemoteTypefaceCache( + "test_cache", 32 * 1024 /* 32 KB */, + base::Bind(&loader::MockLoaderFactory::CreateTypefaceLoader, + base::Unretained(&loader_factory_)))), + font_cache_( + new dom::FontCache(&mrp, // resource_provider + rtc.get(), // remotetypefacecache + ALLOW_THIS_IN_INITIALIZER_LIST(base::Bind( + &FontCacheTest::DummyOnTypefaceLoadEvent, + base::Unretained(this))), + "en-US")) {} + +TEST_F(FontCacheTest, FindPostscriptFont) { + const std::string family_name("Dancing Script"); + const std::string postscript_font_name("DancingScript"); + scoped_ptr<FontCache::FontFaceMap> ffm = + CreateFontFaceMapHelper(family_name, postscript_font_name); + font_cache_->SetFontFaceMap(ffm.Pass()); + + EXPECT_CALL(loader_factory_, CreateTypefaceLoaderMock(_, _, _, _, _)) + .Times(0); + + EXPECT_CALL(mock_resource_provider_, + GetLocalTypefaceIfAvailableMock(postscript_font_name)) + .WillOnce(Return(sample_typeface_)); + + FontListFont::State state; + scoped_refptr<render_tree::Font> f = + font_cache_->TryGetFont(family_name, kNormalUpright, 12.0, &state); + + EXPECT_TRUE(f); +} + +TEST_F(FontCacheTest, UseRemote) { + std::string invalid_postscript_font_name = "DancingScriptInvalidName"; + const std::string family_name("Dancing Script"); + scoped_ptr<FontCache::FontFaceMap> ffm = + CreateFontFaceMapHelper(family_name, invalid_postscript_font_name); + font_cache_->SetFontFaceMap(ffm.Pass()); + + EXPECT_CALL(mock_resource_provider_, + GetLocalTypefaceIfAvailableMock(invalid_postscript_font_name)) + .Times(1); + EXPECT_CALL(loader_factory_, CreateTypefaceLoaderMock(_, _, _, _, _)); + + FontListFont::State state; + scoped_refptr<render_tree::Font> f = + font_cache_->TryGetFont(family_name, kNormalUpright, 12.0, &state); + EXPECT_FALSE(f); +} + +} // namespace dom +} // namespace cobalt
diff --git a/src/cobalt/dom/font_face_updater.cc b/src/cobalt/dom/font_face_updater.cc index dbcd212..16de45f 100644 --- a/src/cobalt/dom/font_face_updater.cc +++ b/src/cobalt/dom/font_face_updater.cc
@@ -158,6 +158,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kNone: case cssom::KeywordValue::kNoRepeat: case cssom::KeywordValue::kNormal: @@ -172,6 +173,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible:
diff --git a/src/cobalt/dom/font_list.cc b/src/cobalt/dom/font_list.cc index f91dc45..69fde52 100644 --- a/src/cobalt/dom/font_list.cc +++ b/src/cobalt/dom/font_list.cc
@@ -240,6 +240,7 @@ } } } + DCHECK(primary_font_); return primary_font_; }
diff --git a/src/cobalt/dom/html_link_element.cc b/src/cobalt/dom/html_link_element.cc index fd85733..7a4802c 100644 --- a/src/cobalt/dom/html_link_element.cc +++ b/src/cobalt/dom/html_link_element.cc
@@ -26,6 +26,7 @@ #include "cobalt/dom/document.h" #include "cobalt/dom/html_element_context.h" #include "googleurl/src/gurl.h" +#include "nb/memory_scope.h" namespace cobalt { namespace dom { @@ -54,6 +55,7 @@ // Algorithm for Obtain: // https://www.w3.org/TR/html5/document-metadata.html#concept-link-obtain void HTMLLinkElement::Obtain() { + TRACK_MEMORY_SCOPE("DOM"); TRACE_EVENT0("cobalt::dom", "HTMLLinkElement::Obtain()"); // Custom, not in any spec. DCHECK(thread_checker_.CalledOnValidThread()); @@ -107,6 +109,7 @@ } void HTMLLinkElement::OnLoadingDone(const std::string& content) { + TRACK_MEMORY_SCOPE("DOM"); DCHECK(thread_checker_.CalledOnValidThread()); TRACE_EVENT0("cobalt::dom", "HTMLLinkElement::OnLoadingDone()");
diff --git a/src/cobalt/dom/html_media_element.cc b/src/cobalt/dom/html_media_element.cc index e0f632c..762a0c3 100644 --- a/src/cobalt/dom/html_media_element.cc +++ b/src/cobalt/dom/html_media_element.cc
@@ -53,6 +53,18 @@ namespace { +#define LOG_MEDIA_ELEMENT_ACTIVITIES 0 + +#if LOG_MEDIA_ELEMENT_ACTIVITIES + +#define MLOG() LOG(INFO) << __FUNCTION__ << ": " + +#else // LOG_MEDIA_ELEMENT_ACTIVITIES + +#define MLOG() EAT_STREAM_PARAMETERS + +#endif // LOG_MEDIA_ELEMENT_ACTIVITIES + void RaiseMediaKeyException(WebMediaPlayer::MediaKeyException exception, script::ExceptionState* exception_state) { DCHECK_NE(exception, WebMediaPlayer::kMediaKeyExceptionNoError); @@ -119,25 +131,35 @@ pending_load_(false), sent_stalled_event_(false), sent_end_event_(false) { + MLOG(); html_media_element_count_log.Get().count++; } HTMLMediaElement::~HTMLMediaElement() { + MLOG(); SetSourceState(MediaSource::kReadyStateClosed); html_media_element_count_log.Get().count--; } +scoped_refptr<MediaError> HTMLMediaElement::error() const { + MLOG() << error_->code(); + return error_; +} + std::string HTMLMediaElement::src() const { + MLOG() << GetAttribute("src").value_or(""); return GetAttribute("src").value_or(""); } void HTMLMediaElement::set_src(const std::string& src) { + MLOG() << src; SetAttribute("src", src); ClearMediaPlayer(); ScheduleLoad(); } uint16_t HTMLMediaElement::network_state() const { + MLOG() << network_state_; return static_cast<uint16_t>(network_state_); } @@ -145,13 +167,17 @@ scoped_refptr<TimeRanges> buffered = new TimeRanges; if (!player_) { + MLOG() << "(empty)"; return buffered; } const ::media::Ranges<base::TimeDelta>& player_buffered = player_->GetBufferedTimeRanges(); + MLOG() << "================================"; for (int i = 0; i < static_cast<int>(player_buffered.size()); ++i) { + MLOG() << player_buffered.start(i).InSecondsF() << " - " + << player_buffered.end(i).InSecondsF(); buffered->Add(player_buffered.start(i).InSecondsF(), player_buffered.end(i).InSecondsF()); } @@ -177,6 +203,7 @@ std::string result = html_element_context()->can_play_type_handler()->CanPlayType(mime_type, key_system); + MLOG() << "(" << mime_type << ", " << key_system << ") => " << result; DLOG(INFO) << "HTMLMediaElement::canPlayType(" << mime_type << ", " << key_system << ") -> " << result; return result; @@ -186,15 +213,18 @@ const std::string& key_system, const base::optional<scoped_refptr<Uint8Array> >& init_data, script::ExceptionState* exception_state) { + MLOG() << key_system; // https://dvcs.w3.org/hg/html-media/raw-file/eme-v0.1b/encrypted-media/encrypted-media.html#dom-generatekeyrequest // 1. If the first argument is null, throw a SYNTAX_ERR. if (key_system.empty()) { + MLOG() << "syntax error"; DOMException::Raise(DOMException::kSyntaxErr, exception_state); return; } // 2. If networkState is NETWORK_EMPTY, throw an INVALID_STATE_ERR. if (network_state_ == kNetworkEmpty || !player_) { + MLOG() << "invalid state error"; DOMException::Raise(DOMException::kInvalidStateErr, exception_state); return; } @@ -211,6 +241,7 @@ } if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) { + MLOG() << "exception: " << exception; RaiseMediaKeyException(exception, exception_state); } } @@ -220,21 +251,25 @@ const base::optional<scoped_refptr<Uint8Array> >& init_data, const base::optional<std::string>& session_id, script::ExceptionState* exception_state) { + MLOG() << key_system; // https://dvcs.w3.org/hg/html-media/raw-file/eme-v0.1b/encrypted-media/encrypted-media.html#dom-addkey // 1. If the first or second argument is null, throw a SYNTAX_ERR. if (key_system.empty() || !key) { + MLOG() << "syntax error"; DOMException::Raise(DOMException::kSyntaxErr, exception_state); return; } // 2. If the second argument is an empty array, throw a TYPE_MISMATCH_ERR. if (!key->length()) { + MLOG() << "type mismatch error"; DOMException::Raise(DOMException::kTypeMismatchErr, exception_state); return; } // 3. If networkState is NETWORK_EMPTY, throw an INVALID_STATE_ERR. if (network_state_ == kNetworkEmpty || !player_) { + MLOG() << "invalid state error"; DOMException::Raise(DOMException::kInvalidStateErr, exception_state); return; } @@ -253,6 +288,7 @@ } if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) { + MLOG() << "exception: " << exception; RaiseMediaKeyException(exception, exception_state); } } @@ -264,11 +300,13 @@ // https://dvcs.w3.org/hg/html-media/raw-file/eme-v0.1b/encrypted-media/encrypted-media.html#dom-addkey // 1. If the first argument is null, throw a SYNTAX_ERR. if (key_system.empty()) { + MLOG() << "syntax error"; DOMException::Raise(DOMException::kSyntaxErr, exception_state); return; } if (!player_) { + MLOG() << "invalid state error"; DOMException::Raise(DOMException::kInvalidStateErr, exception_state); return; } @@ -277,29 +315,38 @@ WebMediaPlayer::MediaKeyException exception = player_->CancelKeyRequest(key_system, session_id.value_or("")); if (exception != WebMediaPlayer::kMediaKeyExceptionNoError) { + MLOG() << "exception: " << exception; RaiseMediaKeyException(exception, exception_state); } } -WebMediaPlayer::ReadyState HTMLMediaElement::ready_state() const { - return ready_state_; +uint16_t HTMLMediaElement::ready_state() const { + MLOG() << ready_state_; + return static_cast<uint16_t>(ready_state_); } -bool HTMLMediaElement::seeking() const { return seeking_; } +bool HTMLMediaElement::seeking() const { + MLOG() << seeking_; + return seeking_; +} float HTMLMediaElement::current_time( script::ExceptionState* exception_state) const { UNREFERENCED_PARAMETER(exception_state); if (!player_) { + MLOG() << 0 << " (because player is NULL)"; return 0; } if (seeking_) { + MLOG() << last_seek_time_ << " (seeking)"; return last_seek_time_; } - return player_->GetCurrentTime(); + float time = player_->GetCurrentTime(); + MLOG() << time << " (from player)"; + return time; } void HTMLMediaElement::set_current_time( @@ -310,36 +357,50 @@ // WebMediaPlayer::kReadyStateHaveNothing, then raise an INVALID_STATE_ERR // exception. if (ready_state_ == WebMediaPlayer::kReadyStateHaveNothing || !player_) { + MLOG() << "invalid state error"; DOMException::Raise(DOMException::kInvalidStateErr, exception_state); return; } + MLOG() << "seek to " << time; Seek(time); } float HTMLMediaElement::duration() const { if (player_ && ready_state_ >= WebMediaPlayer::kReadyStateHaveMetadata) { - return player_->GetDuration(); + float duration = player_->GetDuration(); + MLOG() << "player duration: " << duration; + return duration; } + MLOG() << "NaN"; return std::numeric_limits<float>::quiet_NaN(); } -bool HTMLMediaElement::paused() const { return paused_; } +bool HTMLMediaElement::paused() const { + MLOG() << paused_; + return paused_; +} float HTMLMediaElement::default_playback_rate() const { + MLOG() << default_playback_rate_; return default_playback_rate_; } void HTMLMediaElement::set_default_playback_rate(float rate) { + MLOG() << rate; if (default_playback_rate_ != rate) { default_playback_rate_ = rate; ScheduleEvent(base::Tokens::ratechange()); } } -float HTMLMediaElement::playback_rate() const { return playback_rate_; } +float HTMLMediaElement::playback_rate() const { + MLOG() << playback_rate_; + return playback_rate_; +} void HTMLMediaElement::set_playback_rate(float rate) { + MLOG() << rate; if (playback_rate_ != rate) { playback_rate_ = rate; ScheduleEvent(base::Tokens::ratechange()); @@ -351,6 +412,7 @@ } const scoped_refptr<TimeRanges>& HTMLMediaElement::played() { + MLOG(); if (playing_) { float time = current_time(NULL); if (time > last_seek_time_) { @@ -367,8 +429,11 @@ scoped_refptr<TimeRanges> HTMLMediaElement::seekable() const { if (player_ && player_->GetMaxTimeSeekable() != 0) { - return new TimeRanges(0, player_->GetMaxTimeSeekable()); + double max_time_seekable = player_->GetMaxTimeSeekable(); + MLOG() << "(0, " << max_time_seekable << ")"; + return new TimeRanges(0, max_time_seekable); } + MLOG() << "(empty)"; return new TimeRanges; } @@ -376,12 +441,18 @@ // 4.8.10.8 Playing the media resource // The ended attribute must return true if the media element has ended // playback and the direction of playback is forwards, and false otherwise. - return EndedPlayback() && playback_rate_ > 0; + bool playback_ended = EndedPlayback() && playback_rate_ > 0; + MLOG() << playback_ended; + return playback_ended; } -bool HTMLMediaElement::autoplay() const { return HasAttribute("autoplay"); } +bool HTMLMediaElement::autoplay() const { + MLOG() << HasAttribute("autoplay"); + return HasAttribute("autoplay"); +} void HTMLMediaElement::set_autoplay(bool autoplay) { + MLOG() << autoplay; // The value of 'autoplay' is true when the 'autoplay' attribute is present. // The value of the attribute is irrelevant. if (autoplay) { @@ -391,11 +462,18 @@ } } -bool HTMLMediaElement::loop() const { return loop_; } +bool HTMLMediaElement::loop() const { + MLOG() << loop_; + return loop_; +} -void HTMLMediaElement::set_loop(bool loop) { loop_ = loop; } +void HTMLMediaElement::set_loop(bool loop) { + MLOG() << loop; + loop_ = loop; +} void HTMLMediaElement::Play() { + MLOG(); // 4.8.10.9. Playing the media resource if (!player_ || network_state_ == kNetworkEmpty) { ScheduleLoad(); @@ -421,6 +499,7 @@ } void HTMLMediaElement::Pause() { + MLOG(); // 4.8.10.9. Playing the media resource if (!player_ || network_state_ == kNetworkEmpty) { ScheduleLoad(); @@ -437,36 +516,45 @@ UpdatePlayState(); } -bool HTMLMediaElement::controls() const { return controls_; } +bool HTMLMediaElement::controls() const { + MLOG() << controls_; + return controls_; +} void HTMLMediaElement::set_controls(bool controls) { + MLOG() << controls_; controls_ = controls; ConfigureMediaControls(); } float HTMLMediaElement::volume(script::ExceptionState* exception_state) const { UNREFERENCED_PARAMETER(exception_state); - + MLOG() << volume_; return volume_; } -void HTMLMediaElement::set_volume(float vol, +void HTMLMediaElement::set_volume(float volume, script::ExceptionState* exception_state) { - if (vol < 0.0f || vol > 1.0f) { + MLOG() << volume; + if (volume < 0.0f || volume > 1.0f) { DOMException::Raise(DOMException::kIndexSizeErr, exception_state); return; } - if (volume_ != vol) { - volume_ = vol; + if (volume_ != volume) { + volume_ = volume; UpdateVolume(); ScheduleEvent(base::Tokens::volumechange()); } } -bool HTMLMediaElement::muted() const { return muted_; } +bool HTMLMediaElement::muted() const { + MLOG() << muted_; + return muted_; +} void HTMLMediaElement::set_muted(bool muted) { + MLOG() << muted; if (muted_ != muted) { muted_ = muted; // Avoid recursion when the player reports volume changes. @@ -489,6 +577,7 @@ } void HTMLMediaElement::CreateMediaPlayer() { + MLOG(); if (src().empty()) { reduced_image_cache_capacity_request_ = base::nullopt; } else if (html_element_context() @@ -726,6 +815,7 @@ } void HTMLMediaElement::ClearMediaPlayer() { + MLOG(); SetSourceState(MediaSource::kReadyStateClosed); player_.reset(NULL); @@ -744,6 +834,7 @@ } void HTMLMediaElement::NoneSupported() { + MLOG(); StopPeriodicTimers(); load_state_ = kWaitingForSource; @@ -874,6 +965,7 @@ } void HTMLMediaElement::ScheduleEvent(base::Token event_name) { + MLOG() << event_name; scoped_refptr<Event> event = new Event(event_name, Event::kNotBubbles, Event::kCancelable); event->set_target(this); @@ -1227,6 +1319,7 @@ } void HTMLMediaElement::MediaEngineError(scoped_refptr<MediaError> error) { + MLOG() << error->code(); DLOG(WARNING) << "HTMLMediaElement::MediaEngineError " << error->code(); // 1 - The user agent should cancel the fetching process. @@ -1370,6 +1463,7 @@ void HTMLMediaElement::KeyAdded(const std::string& key_system, const std::string& session_id) { + MLOG() << key_system; event_queue_.Enqueue(new MediaKeyCompleteEvent(key_system, session_id)); } @@ -1377,6 +1471,7 @@ const std::string& session_id, MediaKeyErrorCode error_code, uint16 system_code) { + MLOG() << key_system; MediaKeyError::Code code; switch (error_code) { case kUnknownError: @@ -1411,6 +1506,7 @@ const unsigned char* message, unsigned int message_length, const std::string& default_url) { + MLOG() << key_system; event_queue_.Enqueue(new MediaKeyMessageEvent( key_system, session_id, new Uint8Array(NULL, message, message_length, NULL), default_url)); @@ -1420,12 +1516,14 @@ const std::string& session_id, const unsigned char* init_data, unsigned int init_data_length) { + MLOG() << key_system; event_queue_.Enqueue(new MediaKeyNeededEvent( key_system, session_id, new Uint8Array(NULL, init_data, init_data_length, NULL))); } void HTMLMediaElement::SetSourceState(MediaSource::ReadyState ready_state) { + MLOG() << ready_state; if (!media_source_) { return; }
diff --git a/src/cobalt/dom/html_media_element.h b/src/cobalt/dom/html_media_element.h index 7bd360e..90bfa7c 100644 --- a/src/cobalt/dom/html_media_element.h +++ b/src/cobalt/dom/html_media_element.h
@@ -53,7 +53,7 @@ // Web API: HTMLMediaElement // // Error state - scoped_refptr<MediaError> error() const { return error_; } + scoped_refptr<MediaError> error() const; // Network state std::string src() const; @@ -95,7 +95,7 @@ kHaveEnoughData = WebMediaPlayer::kReadyStateHaveEnoughData, }; - WebMediaPlayer::ReadyState ready_state() const; + uint16_t ready_state() const; bool seeking() const; // Playback state
diff --git a/src/cobalt/dom/html_script_element.cc b/src/cobalt/dom/html_script_element.cc index fcfdf35..5584557 100644 --- a/src/cobalt/dom/html_script_element.cc +++ b/src/cobalt/dom/html_script_element.cc
@@ -32,6 +32,7 @@ #include "cobalt/script/global_environment.h" #include "cobalt/script/script_runner.h" #include "googleurl/src/gurl.h" +#include "nb/memory_scope.h" namespace cobalt { namespace dom { @@ -53,7 +54,8 @@ load_option_(0), inline_script_location_(GetSourceLocationName(), 1, 1), is_sync_load_successful_(false), - prevent_garbage_collection_count_(0) { + prevent_garbage_collection_count_(0), + should_execute_(true) { DCHECK(document->html_element_context()->script_runner()); } @@ -110,6 +112,7 @@ // Algorithm for Prepare: // https://www.w3.org/TR/html5/scripting-1.html#prepare-a-script void HTMLScriptElement::Prepare() { + TRACK_MEMORY_SCOPE("DOM"); // Custom, not in any spec. DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(MessageLoop::current()); @@ -260,12 +263,9 @@ base::Unretained(this))); if (is_sync_load_successful_) { - html_element_context()->script_runner()->Execute( - content_, base::SourceLocation(url_.spec(), 1, 1)); - - // If the script is from an external file, fire a simple event named - // load at the script element. - DispatchEvent(new Event(base::Tokens::load())); + PreventGarbageCollection(); + ExecuteExternal(); + AllowGarbageCollection(); } else { // Executing the script block must just consist of firing a simple event // named error at the element. @@ -497,7 +497,20 @@ void HTMLScriptElement::Execute(const std::string& content, const base::SourceLocation& script_location, bool is_external) { - TRACE_EVENT0("cobalt::dom", "HTMLScriptElement::Execute()"); + TRACK_MEMORY_SCOPE("DOM"); + // If should_execute_ is set to false for whatever reason, then we + // immediately exit. + // When inserted using the document.write() method, script elements execute + // (typically synchronously), but when inserted using innerHTML and + // outerHTML attributes, they do not execute at all. + // https://www.w3.org/TR/html5/scripting-1.html#the-script-element. + if (!should_execute_) { + return; + } + + TRACE_EVENT2("cobalt::dom", "HTMLScriptElement::Execute()", "file_path", + script_location.file_path, "line_number", + script_location.line_number); // Since error is already handled, it is guaranteed the load is successful. // 1. 2. 3. Not needed by Cobalt.
diff --git a/src/cobalt/dom/html_script_element.h b/src/cobalt/dom/html_script_element.h index 17ab6d4..aa523db 100644 --- a/src/cobalt/dom/html_script_element.h +++ b/src/cobalt/dom/html_script_element.h
@@ -62,6 +62,10 @@ SetAttributeEventListener(base::Tokens::readystatechange(), event_listener); } + void set_should_execute(bool should_execute) { + should_execute_ = should_execute; + } + // Custom, not in any spec. // // From Node. @@ -133,6 +137,9 @@ std::string content_; // Active requests disabling garbage collection. int prevent_garbage_collection_count_; + + // Whether or not the script should execute at all. + bool should_execute_; }; } // namespace dom
diff --git a/src/cobalt/dom/int16_array.h b/src/cobalt/dom/int16_array.h new file mode 100644 index 0000000..a4a2821 --- /dev/null +++ b/src/cobalt/dom/int16_array.h
@@ -0,0 +1,30 @@ +/* + * Copyright 2016 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_DOM_INT16_ARRAY_H_ +#define COBALT_DOM_INT16_ARRAY_H_ + +#include "cobalt/dom/typed_array.h" + +namespace cobalt { +namespace dom { + +DEFINE_TYPED_ARRAY(Int16Array, int16); + +} // namespace dom +} // namespace cobalt + +#endif // COBALT_DOM_INT16_ARRAY_H_
diff --git a/src/cobalt/dom/local_storage_database.cc b/src/cobalt/dom/local_storage_database.cc index e5f2d11..6b7a4cc 100644 --- a/src/cobalt/dom/local_storage_database.cc +++ b/src/cobalt/dom/local_storage_database.cc
@@ -19,6 +19,7 @@ #include "base/debug/trace_event.h" #include "cobalt/dom/storage_area.h" #include "cobalt/storage/storage_manager.h" +#include "nb/memory_scope.h" #include "sql/statement.h" namespace cobalt { @@ -30,6 +31,7 @@ const int kLatestLocalStorageSchemaVersion = 1; void SqlInit(storage::SqlContext* sql_context) { + TRACK_MEMORY_SCOPE("Storage"); TRACE_EVENT0("cobalt::storage", "LocalStorage::SqlInit()"); sql::Connection* conn = sql_context->sql_connection(); int schema_version; @@ -74,6 +76,7 @@ void SqlReadValues(const std::string& id, const LocalStorageDatabase::ReadCompletionCallback& callback, storage::SqlContext* sql_context) { + TRACK_MEMORY_SCOPE("Storage"); scoped_ptr<StorageArea::StorageMap> values(new StorageArea::StorageMap); sql::Connection* conn = sql_context->sql_connection(); sql::Statement get_values(conn->GetCachedStatement( @@ -91,6 +94,7 @@ void SqlWrite(const std::string& id, const std::string& key, const std::string& value, storage::SqlContext* sql_context) { + TRACK_MEMORY_SCOPE("Storage"); sql::Connection* conn = sql_context->sql_connection(); sql::Statement write_statement(conn->GetCachedStatement( SQL_FROM_HERE, @@ -105,6 +109,7 @@ void SqlDelete(const std::string& id, const std::string& key, storage::SqlContext* sql_context) { + TRACK_MEMORY_SCOPE("Storage"); sql::Connection* conn = sql_context->sql_connection(); sql::Statement delete_statement( conn->GetCachedStatement(SQL_FROM_HERE, @@ -117,6 +122,7 @@ } void SqlClear(const std::string& id, storage::SqlContext* sql_context) { + TRACK_MEMORY_SCOPE("Storage"); sql::Connection* conn = sql_context->sql_connection(); sql::Statement clear_statement( conn->GetCachedStatement(SQL_FROM_HERE, @@ -142,12 +148,14 @@ void LocalStorageDatabase::ReadAll(const std::string& id, const ReadCompletionCallback& callback) { + TRACK_MEMORY_SCOPE("Storage"); Init(); storage_->GetSqlContext(base::Bind(&SqlReadValues, id, callback)); } void LocalStorageDatabase::Write(const std::string& id, const std::string& key, const std::string& value) { + TRACK_MEMORY_SCOPE("Storage"); Init(); storage_->GetSqlContext(base::Bind(&SqlWrite, id, key, value)); storage_->Flush();
diff --git a/src/cobalt/dom/media_source.cc b/src/cobalt/dom/media_source.cc index fa881bf..9807b48 100644 --- a/src/cobalt/dom/media_source.cc +++ b/src/cobalt/dom/media_source.cc
@@ -21,14 +21,18 @@ #include <vector> #include "base/compiler_specific.h" +#include "base/debug/trace_event.h" #include "base/hash_tables.h" #include "base/lazy_instance.h" #include "base/logging.h" #include "base/string_split.h" #include "base/string_util.h" +#include "cobalt/base/polymorphic_downcast.h" #include "cobalt/base/tokens.h" #include "cobalt/dom/dom_exception.h" +#include "cobalt/dom/dom_settings.h" #include "cobalt/dom/event.h" +#include "cobalt/media/can_play_type_handler.h" namespace cobalt { namespace dom { @@ -70,35 +74,6 @@ } // namespace -void MediaSource::Registry::Register( - const std::string& blob_url, - const scoped_refptr<MediaSource>& media_source) { - DCHECK(media_source); - DCHECK(media_source_registry_.find(blob_url) == media_source_registry_.end()); - media_source_registry_.insert(std::make_pair(blob_url, media_source)); -} - -scoped_refptr<MediaSource> MediaSource::Registry::Retrieve( - const std::string& blob_url) { - MediaSourceRegistry::iterator iter = media_source_registry_.find(blob_url); - if (iter == media_source_registry_.end()) { - DLOG(WARNING) << "Cannot find MediaSource object for blob url " << blob_url; - return NULL; - } - - return iter->second; -} - -void MediaSource::Registry::Unregister(const std::string& blob_url) { - MediaSourceRegistry::iterator iter = media_source_registry_.find(blob_url); - if (iter == media_source_registry_.end()) { - DLOG(WARNING) << "Cannot find MediaSource object for blob url " << blob_url; - return; - } - - media_source_registry_.erase(iter); -} - MediaSource::MediaSource() : ready_state_(kReadyStateClosed), player_(NULL), @@ -252,6 +227,19 @@ player_->SourceEndOfStream(eos_status); } +// static +bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings, + const std::string& type) { + DOMSettings* dom_settings = + base::polymorphic_downcast<DOMSettings*>(settings); + DCHECK(dom_settings); + media::CanPlayTypeHandler* handler = dom_settings->can_play_type_handler(); + DCHECK(handler); + std::string result = handler->CanPlayType(type, ""); + DLOG(INFO) << "MediaSource::IsTypeSupported(" << type << ") -> " << result; + return result == "probably"; +} + void MediaSource::SetPlayer(WebMediaPlayer* player) { // It is possible to reuse a MediaSource object but unlikely. DCHECK it until // it is used in this way. @@ -316,6 +304,8 @@ SetReadyState(kReadyStateOpen); } + TRACE_EVENT1("media_stack", "MediaSource::Append()", "size", size); + // If size is greater than kMaxAppendSize, we will append the data in multiple // small chunks with size less than or equal to kMaxAppendSize. This can // avoid memory allocation spike as ChunkDemuxer may try to allocator memory
diff --git a/src/cobalt/dom/media_source.h b/src/cobalt/dom/media_source.h index ec769e1..b8aa55a 100644 --- a/src/cobalt/dom/media_source.h +++ b/src/cobalt/dom/media_source.h
@@ -25,6 +25,8 @@ #include "cobalt/dom/event_target.h" #include "cobalt/dom/source_buffer.h" #include "cobalt/dom/source_buffer_list.h" +#include "cobalt/dom/url_registry.h" +#include "cobalt/script/environment_settings.h" #include "cobalt/script/exception_state.h" #include "media/player/web_media_player.h" @@ -41,27 +43,7 @@ // Note that our implementation is based on the MSE draft on 09 August 2012. class MediaSource : public EventTarget { public: - // This class manages a registry of MediaSource objects. Its user can - // associate a MediaSource object to a blob url as well as to retrieve a - // MediaSource object by a blob url. This is because to assign a MediaSource - // object to the src of an HTMLMediaElement, we have to first convert the - // MediaSource object into a blob url by calling URL.createObjectURL(). - // And eventually the HTMLMediaElement have to retrieve the MediaSource object - // from the blob url. - // Note: It is unsafe to directly encode the pointer to the MediaSource object - // in the url as the url is assigned from JavaScript. - class Registry { - public: - void Register(const std::string& blob_url, - const scoped_refptr<MediaSource>& media_source); - scoped_refptr<MediaSource> Retrieve(const std::string& blob_url); - void Unregister(const std::string& blob_url); - - private: - typedef base::hash_map<std::string, scoped_refptr<MediaSource> > - MediaSourceRegistry; - MediaSourceRegistry media_source_registry_; - }; + typedef UrlRegistry<MediaSource> Registry; // Custom, not in any spec. // @@ -87,6 +69,9 @@ void EndOfStream(const std::string& error, script::ExceptionState* exception_state); + static bool IsTypeSupported(script::EnvironmentSettings* settings, + const std::string& type); + // Custom, not in any spec. // // The player is set when the media source is attached to a media element.
diff --git a/src/cobalt/dom/node.cc b/src/cobalt/dom/node.cc index 1ec1d0d..cac03aa 100644 --- a/src/cobalt/dom/node.cc +++ b/src/cobalt/dom/node.cc
@@ -37,6 +37,13 @@ #include "cobalt/dom/node_list_live.h" #include "cobalt/dom/rule_matching.h" #include "cobalt/dom/text.h" +#if defined(OS_STARBOARD) +#include "starboard/configuration.h" +#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +#define HANDLE_CORE_DUMP +#include "starboard/ps4/core_dump_handler.h" +#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +#endif // defined(OS_STARBOARD) namespace cobalt { namespace dom { @@ -49,8 +56,25 @@ NodeCountLog() : count(0) { base::UserLog::Register(base::UserLog::kNodeCountIndex, "NodeCnt", &count, sizeof(count)); +#if defined(HANDLE_CORE_DUMP) + SbCoreDumpRegisterHandler(CoreDumpHandler, this); +#endif } - ~NodeCountLog() { base::UserLog::Deregister(base::UserLog::kNodeCountIndex); } + + ~NodeCountLog() { +#if defined(HANDLE_CORE_DUMP) + SbCoreDumpUnregisterHandler(CoreDumpHandler, this); +#endif + base::UserLog::Deregister(base::UserLog::kNodeCountIndex); + } + +#if defined(HANDLE_CORE_DUMP) + static void CoreDumpHandler(void* context) { + SbCoreDumpLogInteger( + "Total number of nodes", + static_cast<NodeCountLog*>(context)->count); + } +#endif int count;
diff --git a/src/cobalt/dom/parser.h b/src/cobalt/dom/parser.h index 554643e..3bf7392 100644 --- a/src/cobalt/dom/parser.h +++ b/src/cobalt/dom/parser.h
@@ -45,7 +45,8 @@ virtual ~Parser() {} // Synchronous parsing interfaces. // - // Parses an HTML document and returns the created Document. + // Parses an HTML document and returns the created Document. No + // script elements within the HTML document will be executed. virtual scoped_refptr<Document> ParseDocument( const std::string& input, HTMLElementContext* html_element_context, const base::SourceLocation& input_location) = 0; @@ -56,7 +57,8 @@ const base::SourceLocation& input_location) = 0; // Parses an HTML input and inserts new nodes in document under parent_node - // before reference_node. + // before reference_node. No script elements within the HTML document will + // be executed. virtual void ParseDocumentFragment( const std::string& input, const scoped_refptr<Document>& document, const scoped_refptr<Node>& parent_node, @@ -74,7 +76,8 @@ // Asynchronous parsing interfaces. // // Parses an HTML document asynchronously, returns the decoder that can be - // used in the parsing. + // used in the parsing. Script elements in the HTML document will be + // executed. virtual scoped_ptr<loader::Decoder> ParseDocumentAsync( const scoped_refptr<Document>& document, const base::SourceLocation& input_location) = 0;
diff --git a/src/cobalt/dom/time_ranges_test.cc b/src/cobalt/dom/time_ranges_test.cc index 4404477..cc8f12f 100644 --- a/src/cobalt/dom/time_ranges_test.cc +++ b/src/cobalt/dom/time_ranges_test.cc
@@ -33,7 +33,14 @@ void SetSimpleException(script::MessageType /*message_type*/, ...) OVERRIDE { // no-op } - scoped_refptr<DOMException> dom_exception_; + dom::DOMException::ExceptionCode GetExceptionCode() { + return dom_exception_ ? static_cast<dom::DOMException::ExceptionCode>( + dom_exception_->code()) + : dom::DOMException::kNone; + } + + private: + scoped_refptr<dom::DOMException> dom_exception_; }; } // namespace @@ -180,16 +187,12 @@ { FakeExceptionState exception_state; time_ranges->Start(2, &exception_state); - ASSERT_TRUE(exception_state.dom_exception_); - EXPECT_EQ(DOMException::kIndexSizeErr, - exception_state.dom_exception_->code()); + EXPECT_EQ(DOMException::kIndexSizeErr, exception_state.GetExceptionCode()); } { FakeExceptionState exception_state; time_ranges->End(2, &exception_state); - ASSERT_TRUE(exception_state.dom_exception_); - EXPECT_EQ(DOMException::kIndexSizeErr, - exception_state.dom_exception_->code()); + EXPECT_EQ(DOMException::kIndexSizeErr, exception_state.GetExceptionCode()); } }
diff --git a/src/cobalt/dom/typed_array.h b/src/cobalt/dom/typed_array.h index 266e002..e901a70 100644 --- a/src/cobalt/dom/typed_array.h +++ b/src/cobalt/dom/typed_array.h
@@ -17,14 +17,16 @@ #ifndef COBALT_DOM_TYPED_ARRAY_H_ #define COBALT_DOM_TYPED_ARRAY_H_ -#include <memory.h> // for memcpy - #include "base/logging.h" #include "base/stringprintf.h" #include "cobalt/dom/array_buffer_view.h" #include "cobalt/script/environment_settings.h" #include "cobalt/script/exception_state.h" +#if defined(STARBOARD) +#include "starboard/memory.h" +#endif + namespace cobalt { namespace dom { @@ -50,7 +52,11 @@ uint32 length) : ArrayBufferView(new ArrayBuffer(settings, length * kBytesPerElement)) { DCHECK_EQ(this->length(), length); +#if defined(STARBOARD) + SbMemoryCopy(this->data(), data, length * kBytesPerElement); +#else memcpy(this->data(), data, length * kBytesPerElement); +#endif } // Creates a new TypedArray and copies the elements of 'other' into this. @@ -123,8 +129,13 @@ } uint32 source_offset = 0; while (source_offset < source->length()) { +#if defined(STARBOARD) + SbMemoryCopy(data() + offset, source->data() + source_offset, + sizeof(ElementType)); +#else memcpy(data() + offset, source->data() + source_offset, sizeof(ElementType)); +#endif ++offset; ++source_offset; } @@ -138,14 +149,22 @@ // Write a single element of the array. void Set(uint32 index, ElementType val) { if (index < length()) { +#if defined(STARBOARD) + SbMemoryCopy(data() + index, &val, sizeof(ElementType)); +#else memcpy(data() + index, &val, sizeof(ElementType)); +#endif } } ElementType Get(uint32 index) const { if (index < length()) { ElementType val; +#if defined(STARBOARD) + SbMemoryCopy(&val, data() + index, sizeof(ElementType)); +#else memcpy(&val, data() + index, sizeof(ElementType)); +#endif return val; } else { // TODO: an out of bounds index should return undefined.
diff --git a/src/cobalt/dom/url.cc b/src/cobalt/dom/url.cc index 7388773..e7b256e 100644 --- a/src/cobalt/dom/url.cc +++ b/src/cobalt/dom/url.cc
@@ -50,6 +50,24 @@ } // static +std::string URL::CreateObjectURL( + script::EnvironmentSettings* environment_settings, + const scoped_refptr<Blob>& blob) { + DOMSettings* dom_settings = + base::polymorphic_downcast<DOMSettings*>(environment_settings); + DCHECK(dom_settings); + DCHECK(dom_settings->blob_registry()); + if (!blob) { + return ""; + } + + std::string blob_url = kBlobUrlProtocol; + blob_url += ':' + base::GenerateGUID(); + dom_settings->blob_registry()->Register(blob_url, blob); + return blob_url; +} + +// static void URL::RevokeObjectURL(script::EnvironmentSettings* environment_settings, const std::string& url) { DOMSettings* dom_settings = @@ -69,7 +87,33 @@ // 2. Otherwise, user agents must remove the entry from the Blob URL Store for // url. - dom_settings->media_source_registry()->Unregister(url); + if (!dom_settings->media_source_registry()->Unregister(url) && + !dom_settings->blob_registry()->Unregister(url)) { + DLOG(WARNING) << "Cannot find object for blob url " << url; + } +} + +bool URL::BlobResolver(dom::Blob::Registry* registry, const GURL& url, + const char** data, size_t* size) { + DCHECK(data); + DCHECK(size); + + *size = 0; + *data = NULL; + + dom::Blob* blob = registry->Retrieve(url.spec()).get(); + + if (blob) { + *size = static_cast<size_t>(blob->size()); + + if (*size > 0) { + *data = reinterpret_cast<const char*>(blob->data()); + } + + return true; + } else { + return false; + } } } // namespace dom
diff --git a/src/cobalt/dom/url.h b/src/cobalt/dom/url.h index f2286aa..6899612 100644 --- a/src/cobalt/dom/url.h +++ b/src/cobalt/dom/url.h
@@ -19,7 +19,9 @@ #include <string> +#include "cobalt/dom/blob.h" #include "cobalt/dom/media_source.h" +#include "cobalt/loader/blob_fetcher.h" #include "cobalt/script/environment_settings.h" #include "cobalt/script/wrappable.h" @@ -34,18 +36,28 @@ // The Media Source Extension extends it to create an url from a MediaSource // object so we can assign it to HTMLMediaElement.src. // https://rawgit.com/w3c/media-source/cfb1b3d4309a6e6e2c01bd87e048758172a86e4b/media-source.html#dom-createobjecturl -// -// Note: We only implemented the MediaSource related functions as they are all -// what we need for Cobalt. class URL : public script::Wrappable { public: static std::string CreateObjectURL( script::EnvironmentSettings* environment_settings, const scoped_refptr<MediaSource>& media_source); + static std::string CreateObjectURL( + script::EnvironmentSettings* environment_settings, + const scoped_refptr<Blob>& blob); static void RevokeObjectURL(script::EnvironmentSettings* environment_settings, const std::string& url); + static loader::BlobFetcher::ResolverCallback MakeBlobResolverCallback( + dom::Blob::Registry* blob_registry) { + DCHECK(blob_registry); + return base::Bind(&BlobResolver, blob_registry); + } + DEFINE_WRAPPABLE_TYPE(URL); + + private: + static bool BlobResolver(dom::Blob::Registry* registry, const GURL& url, + const char** data, size_t* size); }; } // namespace dom
diff --git a/src/cobalt/dom/url_registry.h b/src/cobalt/dom/url_registry.h new file mode 100644 index 0000000..3619762 --- /dev/null +++ b/src/cobalt/dom/url_registry.h
@@ -0,0 +1,84 @@ +/* + * Copyright 2016 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_DOM_URL_REGISTRY_H_ +#define COBALT_DOM_URL_REGISTRY_H_ + +#include <string> + +#include "base/hash_tables.h" +#include "base/memory/ref_counted.h" + +namespace cobalt { +namespace dom { + +// This class manages a registry of objects. Its user can associate a +// object to a blob url as well as to retrieve a object by a blob url. +// This is because to assign a object to the src of an HTMLMediaElement +// (when the object is MediaSource) or to another kind of element or CSS +// property (when the object is a Blob), we have to first convert the +// object into a blob url by calling URL.createObjectURL(). And eventually +// the element or property has to retrieve the object from the blob url. +// Note: It is unsafe to directly encode the pointer to the object in the +// url as the url is assigned from JavaScript. +template <typename ObjectType> +class UrlRegistry { + public: + void Register(const std::string& blob_url, + const scoped_refptr<ObjectType>& object); + scoped_refptr<ObjectType> Retrieve(const std::string& blob_url); + bool Unregister(const std::string& blob_url); + + private: + typedef base::hash_map<std::string, scoped_refptr<ObjectType> > UrlMap; + UrlMap object_registry_; +}; + +template <typename ObjectType> +void UrlRegistry<ObjectType>::Register( + const std::string& blob_url, const scoped_refptr<ObjectType>& object) { + DCHECK(object); + DCHECK(object_registry_.find(blob_url) == object_registry_.end()); + object_registry_.insert(std::make_pair(blob_url, object)); +} + +template <typename ObjectType> +scoped_refptr<ObjectType> UrlRegistry<ObjectType>::Retrieve( + const std::string& blob_url) { + typename UrlMap::iterator iter = object_registry_.find(blob_url); + if (iter == object_registry_.end()) { + DLOG(WARNING) << "Cannot find object for blob url " << blob_url; + return NULL; + } + + return iter->second; +} + +template <typename ObjectType> +bool UrlRegistry<ObjectType>::Unregister(const std::string& blob_url) { + typename UrlMap::iterator iter = object_registry_.find(blob_url); + if (iter == object_registry_.end()) { + return false; + } + + object_registry_.erase(iter); + return true; +} + +} // namespace dom +} // namespace cobalt + +#endif // COBALT_DOM_URL_REGISTRY_H_
diff --git a/src/cobalt/dom/url_utils.cc b/src/cobalt/dom/url_utils.cc index a31a07f..0ff0097 100644 --- a/src/cobalt/dom/url_utils.cc +++ b/src/cobalt/dom/url_utils.cc
@@ -89,9 +89,15 @@ return url_.has_ref() ? "#" + url_.ref() : ""; } +// Algorithm for set_hash: +// https://www.w3.org/TR/2014/WD-url-1-20141209/#dom-urlutils-hash void URLUtils::set_hash(const std::string& hash) { + // 3. Let input be the given value with a single leading "#" removed, if any. + std::string hash_value = + !hash.empty() && hash[0] == '#' ? hash.substr(1) : hash; + GURL::Replacements replacements; - replacements.SetRefStr(hash); + replacements.SetRefStr(hash_value); GURL new_url = url_.ReplaceComponents(replacements); RunPreUpdateSteps(new_url, ""); } @@ -100,9 +106,15 @@ return url_.has_query() ? "?" + url_.query() : ""; } +// Algorithm for set_search: +// https://www.w3.org/TR/2014/WD-url-1-20141209/#dom-urlutils-search void URLUtils::set_search(const std::string& search) { + // 3. Let input be the given value with a single leading "?" removed, if any. + std::string search_value = + !search.empty() && search[0] == '?' ? search.substr(1) : search; + GURL::Replacements replacements; - replacements.SetQueryStr(search); + replacements.SetQueryStr(search_value); GURL new_url = url_.ReplaceComponents(replacements); RunPreUpdateSteps(new_url, ""); } @@ -111,6 +123,8 @@ // https://www.w3.org/TR/2014/WD-url-1-20141209/#pre-update-steps void URLUtils::RunPreUpdateSteps(const GURL& new_url, const std::string& value) { + DLOG(INFO) << "Update URL to " << new_url.spec(); + // 1. If value is not given, let value be the result of serializing the // associated url. // 2, Run the update steps with value.
diff --git a/src/cobalt/dom/url_utils_test.cc b/src/cobalt/dom/url_utils_test.cc index 133b8b9..29c4721 100644 --- a/src/cobalt/dom/url_utils_test.cc +++ b/src/cobalt/dom/url_utils_test.cc
@@ -92,6 +92,11 @@ EXPECT_TRUE(url_utils.url().is_valid()); EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#hash", url_utils.href()); + + url_utils.set_hash("#hash2"); + EXPECT_TRUE(url_utils.url().is_valid()); + EXPECT_EQ("https://user:pass@google.com:99/foo;bar?q=a#hash2", + url_utils.href()); } TEST(URLUtilsTest, SetSearchShouldWorkAsExpected) { @@ -100,6 +105,11 @@ EXPECT_TRUE(url_utils.url().is_valid()); EXPECT_EQ("https://user:pass@google.com:99/foo;bar?b=c#ref", url_utils.href()); + + url_utils.set_search("?d=e"); + EXPECT_TRUE(url_utils.url().is_valid()); + EXPECT_EQ("https://user:pass@google.com:99/foo;bar?d=e#ref", + url_utils.href()); } } // namespace dom
diff --git a/src/cobalt/dom_parser/html_decoder.cc b/src/cobalt/dom_parser/html_decoder.cc index ddda365..f0b5585 100644 --- a/src/cobalt/dom_parser/html_decoder.cc +++ b/src/cobalt/dom_parser/html_decoder.cc
@@ -30,12 +30,14 @@ const scoped_refptr<dom::Node>& reference_node, const int dom_max_element_depth, const base::SourceLocation& input_location, const base::Closure& done_callback, - const base::Callback<void(const std::string&)>& error_callback) + const base::Callback<void(const std::string&)>& error_callback, + const bool should_run_scripts) : libxml_html_parser_wrapper_(new LibxmlHTMLParserWrapper( document, parent_node, reference_node, dom_max_element_depth, - input_location, error_callback)), + input_location, error_callback, should_run_scripts)), document_(document), - done_callback_(done_callback) {} + done_callback_(done_callback), + should_run_scripts_(should_run_scripts) {} HTMLDecoder::~HTMLDecoder() {}
diff --git a/src/cobalt/dom_parser/html_decoder.h b/src/cobalt/dom_parser/html_decoder.h index f9d4da0..cf57cf9 100644 --- a/src/cobalt/dom_parser/html_decoder.h +++ b/src/cobalt/dom_parser/html_decoder.h
@@ -50,7 +50,8 @@ const int dom_max_element_depth, const base::SourceLocation& input_location, const base::Closure& done_callback, - const base::Callback<void(const std::string&)>& error_callback); + const base::Callback<void(const std::string&)>& error_callback, + const bool should_run_scripts); ~HTMLDecoder(); @@ -74,6 +75,8 @@ base::ThreadChecker thread_checker_; const base::Closure done_callback_; + const bool should_run_scripts_; + DISALLOW_COPY_AND_ASSIGN(HTMLDecoder); };
diff --git a/src/cobalt/dom_parser/html_decoder_test.cc b/src/cobalt/dom_parser/html_decoder_test.cc index ab5f6a1..70019a1 100644 --- a/src/cobalt/dom_parser/html_decoder_test.cc +++ b/src/cobalt/dom_parser/html_decoder_test.cc
@@ -82,7 +82,8 @@ html_decoder_.reset(new HTMLDecoder( document_, document_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -104,7 +105,8 @@ html_decoder_.reset(new HTMLDecoder( document_, document_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(reinterpret_cast<char*>(temp), sizeof(temp)); html_decoder_->Finish(); @@ -122,7 +124,8 @@ html_decoder_.reset(new HTMLDecoder( document_, document_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -140,7 +143,8 @@ html_decoder_.reset(new HTMLDecoder( document_, document_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -158,7 +162,8 @@ html_decoder_.reset(new HTMLDecoder( document_, document_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -176,7 +181,8 @@ html_decoder_.reset(new HTMLDecoder( document_, document_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -199,7 +205,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -214,7 +221,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -238,7 +246,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -258,7 +267,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -276,7 +286,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -295,7 +306,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(reinterpret_cast<char*>(temp), sizeof(temp)); html_decoder_->Finish(); @@ -308,7 +320,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -335,7 +348,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -354,7 +368,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -367,6 +382,34 @@ EXPECT_EQ("💩", text->data()); } +TEST_F(HTMLDecoderTest, CanParseUTF8SplitInChunks) { + const std::string input = "<p>💩</p>"; + + for (size_t first_chunk_size = 0; first_chunk_size < input.length(); + first_chunk_size++) { + root_ = new dom::Element(document_, base::Token("element")); + html_decoder_.reset(new HTMLDecoder( + document_, root_, NULL, kDOMMaxElementDepth, source_location_, + base::Closure(), base::Bind(&MockErrorCallback::Run, + base::Unretained(&mock_error_callback_)), + true)); + + // This could cut the input in the middle of a UTF8 character. + html_decoder_->DecodeChunk(input.c_str(), first_chunk_size); + html_decoder_->DecodeChunk(input.c_str() + first_chunk_size, + input.length() - first_chunk_size); + html_decoder_->Finish(); + + dom::Element* element = root_->first_element_child(); + ASSERT_TRUE(element); + EXPECT_EQ("p", element->tag_name()); + + dom::Text* text = element->first_child()->AsText(); + ASSERT_TRUE(text); + EXPECT_EQ("💩", text->data()); + } +} + // Misnested tags: <b><i></b></i> // https://www.w3.org/TR/html5/syntax.html#misnested-tags:-b-i-/b-/i // @@ -376,7 +419,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -402,7 +446,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -420,7 +465,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -434,7 +480,8 @@ html_decoder_.reset(new HTMLDecoder( document_, root_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish(); @@ -457,7 +504,8 @@ html_decoder_.reset(new HTMLDecoder( document_, document_, NULL, kDOMMaxElementDepth, source_location_, base::Closure(), base::Bind(&MockErrorCallback::Run, - base::Unretained(&mock_error_callback_)))); + base::Unretained(&mock_error_callback_)), + true)); html_decoder_->DecodeChunk(input.c_str(), input.length()); html_decoder_->Finish();
diff --git a/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc index 5a0b703..7a05cb8 100644 --- a/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc +++ b/src/cobalt/dom_parser/libxml_html_parser_wrapper.cc
@@ -18,6 +18,8 @@ #include "base/basictypes.h" #include "base/string_util.h" +#include "cobalt/dom/element.h" +#include "cobalt/dom/html_script_element.h" namespace cobalt { namespace dom_parser { @@ -89,6 +91,17 @@ if (!IsFullDocument() && (name == "html" || name == "body")) { return; } + + // If the top if the node stack is an html script element, then set its + // should_execute_ field to be our should_run_scripts_ field. + DCHECK(!node_stack().empty()); + if (name == "script") { + scoped_refptr<dom::HTMLScriptElement> html_script_element = + node_stack().top()->AsElement()->AsHTMLElement()->AsHTMLScriptElement(); + DCHECK(html_script_element); + html_script_element->set_should_execute(should_run_scripts_); + } + LibxmlParserWrapper::OnEndElement(name); } @@ -97,7 +110,10 @@ return; } - if (CheckInputAndUpdateSeverity(data, size) == kFatal) { + std::string current_chunk; + PreprocessChunk(data, size, ¤t_chunk); + + if (max_severity() == kFatal) { return; } @@ -107,9 +123,10 @@ // when used for setting an element's innerHTML. htmlEmitImpliedRootLevelParagraph(0); - html_parser_context_ = htmlCreatePushParserCtxt( - &html_sax_handler, this, data, static_cast<int>(size), - NULL /*filename*/, XML_CHAR_ENCODING_UTF8); + html_parser_context_ = + htmlCreatePushParserCtxt(&html_sax_handler, this, current_chunk.c_str(), + static_cast<int>(current_chunk.size()), + NULL /*filename*/, XML_CHAR_ENCODING_UTF8); if (!html_parser_context_) { static const char kErrorUnableCreateParser[] = @@ -122,7 +139,8 @@ } } else { DCHECK(html_parser_context_); - htmlParseChunk(html_parser_context_, data, static_cast<int>(size), + htmlParseChunk(html_parser_context_, current_chunk.c_str(), + static_cast<int>(current_chunk.size()), 0 /*do not terminate*/); } }
diff --git a/src/cobalt/dom_parser/libxml_html_parser_wrapper.h b/src/cobalt/dom_parser/libxml_html_parser_wrapper.h index f553818..7114443 100644 --- a/src/cobalt/dom_parser/libxml_html_parser_wrapper.h +++ b/src/cobalt/dom_parser/libxml_html_parser_wrapper.h
@@ -40,11 +40,13 @@ const scoped_refptr<dom::Node>& reference_node, const int dom_max_element_depth, const base::SourceLocation& input_location, - const base::Callback<void(const std::string&)>& error_callback) + const base::Callback<void(const std::string&)>& error_callback, + const bool should_run_scripts) : LibxmlParserWrapper(document, parent_node, reference_node, dom_max_element_depth, input_location, error_callback), - html_parser_context_(NULL) { + html_parser_context_(NULL), + should_run_scripts_(should_run_scripts) { DCHECK(!document->IsXMLDocument()); } ~LibxmlHTMLParserWrapper() OVERRIDE; @@ -61,6 +63,8 @@ htmlParserCtxtPtr html_parser_context_; + const bool should_run_scripts_; + DISALLOW_COPY_AND_ASSIGN(LibxmlHTMLParserWrapper); };
diff --git a/src/cobalt/dom_parser/libxml_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_parser_wrapper.cc index 035405b..a86753e 100644 --- a/src/cobalt/dom_parser/libxml_parser_wrapper.cc +++ b/src/cobalt/dom_parser/libxml_parser_wrapper.cc
@@ -19,15 +19,88 @@ #include "base/logging.h" #include "base/string_util.h" #include "base/stringprintf.h" +#include "base/third_party/icu/icu_utf.h" +#include "base/utf_string_conversion_utils.h" +#include "cobalt/base/tokens.h" #include "cobalt/dom/cdata_section.h" #include "cobalt/dom/comment.h" #include "cobalt/dom/element.h" #include "cobalt/dom/text.h" +#if defined(OS_STARBOARD) +#include "starboard/configuration.h" +#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +#define HANDLE_CORE_DUMP +#include "base/lazy_instance.h" +#include "starboard/ps4/core_dump_handler.h" +#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +#endif // defined(OS_STARBOARD) namespace cobalt { namespace dom_parser { namespace { +#if defined(HANDLE_CORE_DUMP) + +class LibxmlParserWrapperLog { + public: + LibxmlParserWrapperLog() + : total_parsed_bytes_(0), + total_warning_count_(0), + total_error_count_(0), + total_fatal_count_(0) { + SbCoreDumpRegisterHandler(CoreDumpHandler, this); + } + ~LibxmlParserWrapperLog() { + SbCoreDumpUnregisterHandler(CoreDumpHandler, this); + } + + static void CoreDumpHandler(void* context) { + SbCoreDumpLogInteger( + "LibxmlParserWrapper total parsed bytes", + static_cast<LibxmlParserWrapperLog*>(context)->total_parsed_bytes_); + SbCoreDumpLogInteger( + "LibxmlParserWrapper total warning count", + static_cast<LibxmlParserWrapperLog*>(context)->total_warning_count_); + SbCoreDumpLogInteger( + "LibxmlParserWrapper total error count", + static_cast<LibxmlParserWrapperLog*>(context)->total_error_count_); + SbCoreDumpLogInteger( + "LibxmlParserWrapper total fatal error count", + static_cast<LibxmlParserWrapperLog*>(context)->total_fatal_count_); + SbCoreDumpLogString("LibxmlParserWrapper last fatal error", + static_cast<LibxmlParserWrapperLog*>(context) + ->last_fatal_message_.c_str()); + } + + void IncrementParsedBytes(int length) { total_parsed_bytes_ += length; } + void LogParsingIssue(LibxmlParserWrapper::IssueSeverity severity, + const std::string& message) { + if (severity == LibxmlParserWrapper::kWarning) { + total_warning_count_++; + } else if (severity == LibxmlParserWrapper::kError) { + total_error_count_++; + } else if (severity == LibxmlParserWrapper::kFatal) { + total_fatal_count_++; + last_fatal_message_ = message; + } else { + NOTREACHED(); + } + } + + private: + int total_parsed_bytes_; + int total_warning_count_; + int total_error_count_; + int total_fatal_count_; + std::string last_fatal_message_; + DISALLOW_COPY_AND_ASSIGN(LibxmlParserWrapperLog); +}; + +base::LazyInstance<LibxmlParserWrapperLog> libxml_parser_wrapper_log = + LAZY_INSTANCE_INITIALIZER; + +#endif // defined(HANDLE_CORE_DUMP) + ///////////////////////////////////////////////////////////////////////////// // Helpers ///////////////////////////////////////////////////////////////////////////// @@ -150,6 +223,10 @@ if (!node_stack_.empty() && !error_callback_.is_null()) { error_callback_.Run("Node stack not empty at end of document."); } + + if (IsFullDocument()) { + document_->PostToDispatchEvent(FROM_HERE, base::Tokens::domcontentloaded()); + } } void LibxmlParserWrapper::OnStartElement( @@ -221,42 +298,50 @@ if (severity < LibxmlParserWrapper::kFatal) { LOG(WARNING) << "Libxml " << (severity == kWarning ? "Warning: " : "Error: ") << message; - } else { + } else if (severity == LibxmlParserWrapper::kFatal) { LOG(ERROR) << "Libxml Fatal Error: " << message; if (!error_callback_.is_null()) { error_callback_.Run(message); } + } else { + NOTREACHED(); } + +#if defined(HANDLE_CORE_DUMP) + libxml_parser_wrapper_log.Get().LogParsingIssue(severity, message); +#endif } void LibxmlParserWrapper::OnCDATABlock(const std::string& value) { node_stack_.top()->AppendChild(new dom::CDATASection(document_, value)); } -LibxmlParserWrapper::IssueSeverity -LibxmlParserWrapper::CheckInputAndUpdateSeverity(const char* data, - size_t size) { - if (max_severity_ == kFatal) { - return max_severity_; - } - +void LibxmlParserWrapper::PreprocessChunk(const char* data, size_t size, + std::string* current_chunk) { + DCHECK(current_chunk); // Check the total input size. total_input_size_ += size; if (total_input_size_ > kMaxTotalInputSize) { static const char kMessageInputTooLong[] = "Parser input is too long."; OnParsingIssue(kFatal, kMessageInputTooLong); - return max_severity_; + return; } // Check the encoding of the input. - if (!IsStringUTF8(std::string(data, size))) { + std::string input = next_chunk_start_ + std::string(data, size); + TruncateUTF8ToByteSize(input, input.size(), current_chunk); + next_chunk_start_ = input.substr(current_chunk->size()); + if (!IsStringUTF8(*current_chunk)) { + current_chunk->clear(); static const char kMessageInputNotUTF8[] = "Parser input contains non-UTF8 characters."; OnParsingIssue(kFatal, kMessageInputNotUTF8); - return max_severity_; + return; } - return max_severity_; +#if defined(HANDLE_CORE_DUMP) + libxml_parser_wrapper_log.Get().IncrementParsedBytes(static_cast<int>(size)); +#endif } } // namespace dom_parser
diff --git a/src/cobalt/dom_parser/libxml_parser_wrapper.h b/src/cobalt/dom_parser/libxml_parser_wrapper.h index 98d210e..4c5e233 100644 --- a/src/cobalt/dom_parser/libxml_parser_wrapper.h +++ b/src/cobalt/dom_parser/libxml_parser_wrapper.h
@@ -121,8 +121,10 @@ // Returns true when the input is a full document, false when it's a fragment. bool IsFullDocument() { return document_ == parent_node_; } - // Checks the input, updates and returns the maximum issue severity. - IssueSeverity CheckInputAndUpdateSeverity(const char* data, size_t size); + // Preprocesses the input chunk and updates the max error severity level. + // Sets current chunk if successful. + void PreprocessChunk(const char* data, size_t size, + std::string* current_chunk); const scoped_refptr<dom::Document>& document() { return document_; } const base::SourceLocation& first_chunk_location() { @@ -136,6 +138,8 @@ return node_stack_; } + IssueSeverity max_severity() const { return max_severity_; } + private: // Maximum total input size, as specified in Libxml's value // XML_MAX_TEXT_LENGTH in parserInternals.h. @@ -154,6 +158,7 @@ IssueSeverity max_severity_; size_t total_input_size_; + std::string next_chunk_start_; std::stack<scoped_refptr<dom::Node> > node_stack_; DISALLOW_COPY_AND_ASSIGN(LibxmlParserWrapper);
diff --git a/src/cobalt/dom_parser/libxml_xml_parser_wrapper.cc b/src/cobalt/dom_parser/libxml_xml_parser_wrapper.cc index 787970d..dfbb290 100644 --- a/src/cobalt/dom_parser/libxml_xml_parser_wrapper.cc +++ b/src/cobalt/dom_parser/libxml_xml_parser_wrapper.cc
@@ -79,14 +79,17 @@ return; } - if (CheckInputAndUpdateSeverity(data, size) == kFatal) { + std::string current_chunk; + PreprocessChunk(data, size, ¤t_chunk); + + if (max_severity() == kFatal) { return; } if (!xml_parser_context_) { - xml_parser_context_ = - xmlCreatePushParserCtxt(&xml_sax_handler, this, data, - static_cast<int>(size), NULL /*filename*/); + xml_parser_context_ = xmlCreatePushParserCtxt( + &xml_sax_handler, this, current_chunk.c_str(), + static_cast<int>(current_chunk.size()), NULL /*filename*/); if (!xml_parser_context_) { static const char kErrorUnableCreateParser[] = @@ -94,7 +97,8 @@ OnParsingIssue(kFatal, kErrorUnableCreateParser); } } else { - xmlParseChunk(xml_parser_context_, data, static_cast<int>(size), + xmlParseChunk(xml_parser_context_, current_chunk.c_str(), + static_cast<int>(current_chunk.size()), 0 /*do not terminate*/); } }
diff --git a/src/cobalt/dom_parser/parser.cc b/src/cobalt/dom_parser/parser.cc index 507c187..5be05a6 100644 --- a/src/cobalt/dom_parser/parser.cc +++ b/src/cobalt/dom_parser/parser.cc
@@ -32,7 +32,8 @@ scoped_refptr<dom::Document> document = new dom::Document(html_element_context, dom::Document::Options()); HTMLDecoder html_decoder(document, document, NULL, dom_max_element_depth_, - input_location, base::Closure(), error_callback_); + input_location, base::Closure(), error_callback_, + false); html_decoder.DecodeChunk(input.c_str(), input.length()); html_decoder.Finish(); return document; @@ -60,7 +61,7 @@ const base::SourceLocation& input_location) { HTMLDecoder html_decoder(document, parent_node, reference_node, dom_max_element_depth_, input_location, - base::Closure(), error_callback_); + base::Closure(), error_callback_, false); html_decoder.DecodeChunk(input.c_str(), input.length()); html_decoder.Finish(); } @@ -84,7 +85,7 @@ const base::SourceLocation& input_location) { return scoped_ptr<loader::Decoder>( new HTMLDecoder(document, document, NULL, dom_max_element_depth_, - input_location, base::Closure(), error_callback_)); + input_location, base::Closure(), error_callback_, true)); } scoped_ptr<loader::Decoder> Parser::ParseXMLDocumentAsync(
diff --git a/src/cobalt/h5vcc/h5vcc_runtime.cc b/src/cobalt/h5vcc/h5vcc_runtime.cc index 82437b4..43d71cc 100644 --- a/src/cobalt/h5vcc/h5vcc_runtime.cc +++ b/src/cobalt/h5vcc/h5vcc_runtime.cc
@@ -14,9 +14,10 @@ * limitations under the License. */ +#include "cobalt/h5vcc/h5vcc_runtime.h" + #include "cobalt/base/deep_link_event.h" #include "cobalt/base/polymorphic_downcast.h" -#include "cobalt/h5vcc/h5vcc_runtime.h" #include "cobalt/system_window/application_event.h" namespace cobalt { @@ -79,8 +80,10 @@ void H5vccRuntime::OnDeepLinkEvent(const base::Event* event) { const base::DeepLinkEvent* deep_link_event = base::polymorphic_downcast<const base::DeepLinkEvent*>(event); - DLOG(INFO) << "Got deep link event: " << deep_link_event->link(); - on_deep_link()->DispatchEvent(deep_link_event->link()); + if (!deep_link_event->IsH5vccLink()) { + DLOG(INFO) << "Got deep link event: " << deep_link_event->link(); + on_deep_link()->DispatchEvent(deep_link_event->link()); + } } } // namespace h5vcc } // namespace cobalt
diff --git a/src/cobalt/layout/box.cc b/src/cobalt/layout/box.cc index 4a57476..a04ced7 100644 --- a/src/cobalt/layout/box.cc +++ b/src/cobalt/layout/box.cc
@@ -16,6 +16,7 @@ #include "cobalt/layout/box.h" +#include <algorithm> #include <limits> #include "base/logging.h" @@ -472,10 +473,17 @@ // 'visibility: visible'. // https://www.w3.org/TR/CSS21/visufx.html#propdef-visibility if (computed_style()->visibility() == cssom::KeywordValue::GetVisible()) { - RenderAndAnimateBackgroundColor( - padding_rounded_corners, &border_node_builder, &animate_node_builder); - RenderAndAnimateBackgroundImage( - padding_rounded_corners, &border_node_builder, &animate_node_builder); + RenderAndAnimateBackgroundImageResult background_image_result = + RenderAndAnimateBackgroundImage(padding_rounded_corners); + // If the background image is opaque, then it will occlude the background + // color and so we do not need to render the background color. + if (!background_image_result.is_opaque) { + RenderAndAnimateBackgroundColor( + padding_rounded_corners, &border_node_builder, &animate_node_builder); + } + if (background_image_result.node) { + border_node_builder.AddChild(background_image_result.node); + } RenderAndAnimateBorder(border_radius_provider.rounded_corners(), &border_node_builder, &animate_node_builder); RenderAndAnimateBoxShadow(border_radius_provider.rounded_corners(), @@ -900,7 +908,7 @@ style->opacity().get())->value(); if (opacity < 1.0f) { - filter_node_builder->opacity_filter.emplace(opacity); + filter_node_builder->opacity_filter.emplace(std::max(0.0f, opacity)); } else { // If opacity is 1, then no opacity filter should be applied, so the // source render tree should appear fully opaque. @@ -1066,11 +1074,16 @@ } } -void Box::RenderAndAnimateBackgroundImage( - const base::optional<RoundedCorners>& rounded_corners, - CompositionNode::Builder* border_node_builder, - AnimateNode::Builder* animate_node_builder) { - UNREFERENCED_PARAMETER(animate_node_builder); +Box::RenderAndAnimateBackgroundImageResult Box::RenderAndAnimateBackgroundImage( + const base::optional<RoundedCorners>& rounded_corners) { + RenderAndAnimateBackgroundImageResult result; + // We track a single render tree node because most of the time there will only + // be one. If there is more, we set |single_node| to NULL and instead + // populate |composition|. The code here tries to avoid using CompositionNode + // if possible to avoid constructing an std::vector. + scoped_refptr<render_tree::Node> single_node = NULL; + base::optional<CompositionNode::Builder> composition; + result.is_opaque = false; math::RectF image_frame( math::PointF(border_left_width().toFloat(), border_top_width().toFloat()), @@ -1105,9 +1118,34 @@ background_node = new FilterNode(filter_node_builder); } - border_node_builder->AddChild(background_node); + // If any of the background image layers are opaque, we set that the + // background image is opaque. This is used to avoid setting up the + // background color if the background image is just going to cover it + // anyway. + result.is_opaque |= background_node_provider.is_opaque(); + + // If this is not the first node to return, then our |single_node| + // shortcut won't work, copy that single node into |composition| before + // continuing. + if (single_node) { + composition.emplace(); + composition->AddChild(single_node); + single_node = NULL; + } + if (!composition) { + single_node = background_node; + } else { + composition->AddChild(background_node); + } } } + + if (single_node) { + result.node = single_node; + } else if (composition) { + result.node = new CompositionNode(composition->Pass()); + } + return result; } scoped_refptr<render_tree::Node> Box::RenderAndAnimateOpacity( @@ -1118,7 +1156,7 @@ FilterNode::Builder filter_node_builder(border_node); if (opacity < 1.0f) { - filter_node_builder.opacity_filter = OpacityFilter(opacity); + filter_node_builder.opacity_filter.emplace(std::max(0.0f, opacity)); } scoped_refptr<FilterNode> filter_node = new FilterNode(filter_node_builder);
diff --git a/src/cobalt/layout/box.h b/src/cobalt/layout/box.h index 676b04d..60e9ccb 100644 --- a/src/cobalt/layout/box.h +++ b/src/cobalt/layout/box.h
@@ -603,10 +603,17 @@ const base::optional<render_tree::RoundedCorners>& rounded_corners, render_tree::CompositionNode::Builder* border_node_builder, render_tree::animations::AnimateNode::Builder* animate_node_builder); - void RenderAndAnimateBackgroundImage( - const base::optional<render_tree::RoundedCorners>& rounded_corners, - render_tree::CompositionNode::Builder* border_node_builder, - render_tree::animations::AnimateNode::Builder* animate_node_builder); + struct RenderAndAnimateBackgroundImageResult { + // The node representing the background image (may be a CompositionNode if + // there are multiple layers). + scoped_refptr<render_tree::Node> node; + // Returns whether the background image opaquely fills the entire frame. + // If true, then we don't need to even consider rendering the background + // color, since it will be occluded by the image. + bool is_opaque; + }; + RenderAndAnimateBackgroundImageResult RenderAndAnimateBackgroundImage( + const base::optional<render_tree::RoundedCorners>& rounded_corners); void RenderAndAnimateBoxShadow( const base::optional<render_tree::RoundedCorners>& rounded_corners, render_tree::CompositionNode::Builder* border_node_builder,
diff --git a/src/cobalt/layout/box_generator.cc b/src/cobalt/layout/box_generator.cc index d19d55e..7b646c0 100644 --- a/src/cobalt/layout/box_generator.cc +++ b/src/cobalt/layout/box_generator.cc
@@ -247,6 +247,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kMonospace: case cssom::KeywordValue::kNoRepeat: case cssom::KeywordValue::kNormal: @@ -263,6 +264,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible: @@ -541,6 +544,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kMonospace: case cssom::KeywordValue::kNoRepeat: case cssom::KeywordValue::kNormal: @@ -557,6 +561,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible: @@ -688,6 +694,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kMonospace: case cssom::KeywordValue::kNoRepeat: case cssom::KeywordValue::kNoWrap: @@ -703,6 +710,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible:
diff --git a/src/cobalt/layout/container_box.cc b/src/cobalt/layout/container_box.cc index 1d55190..a2c79bb 100644 --- a/src/cobalt/layout/container_box.cc +++ b/src/cobalt/layout/container_box.cc
@@ -400,7 +400,7 @@ Vector2dLayoutUnit GetOffsetFromContainingBlockToStackingContext( Box* child_box) { - DCHECK(child_box->IsPositioned()); + DCHECK(child_box->IsPositioned() || child_box->IsTransformed()); Vector2dLayoutUnit relative_position; for (Box *containing_block = child_box->GetContainingBlock(), @@ -454,7 +454,7 @@ if (!current_box) { // Positioned elements may have their containing block farther // up the hierarchy than the stacking context, so handle this case here. - DCHECK(child_box->IsPositioned()); + DCHECK(child_box->IsPositioned() || child_box->IsTransformed()); return -GetOffsetFromContainingBlockToStackingContext(child_box); } #if !defined(NDEBUG)
diff --git a/src/cobalt/layout/layout.cc b/src/cobalt/layout/layout.cc index 46ca1c1..bd61bde 100644 --- a/src/cobalt/layout/layout.cc +++ b/src/cobalt/layout/layout.cc
@@ -78,7 +78,7 @@ *initial_containing_block = initial_containing_block_creation_results.box; // Generate boxes. - { + if (document->html()) { TRACE_EVENT0("cobalt::layout", kBenchmarkStatBoxGeneration); base::StopWatch stop_watch_box_generation( LayoutStatTracker::kStopWatchTypeBoxGeneration,
diff --git a/src/cobalt/layout/replaced_box.cc b/src/cobalt/layout/replaced_box.cc index 1b1c865..7bcb96a 100644 --- a/src/cobalt/layout/replaced_box.cc +++ b/src/cobalt/layout/replaced_box.cc
@@ -20,7 +20,9 @@ #include "base/bind.h" #include "base/logging.h" +#include "cobalt/cssom/filter_function_list_value.h" #include "cobalt/cssom/keyword_value.h" +#include "cobalt/cssom/mtm_function.h" #include "cobalt/layout/container_box.h" #include "cobalt/layout/letterboxed_image.h" #include "cobalt/layout/used_style.h" @@ -42,10 +44,11 @@ using render_tree::CompositionNode; using render_tree::FilterNode; using render_tree::ImageNode; +using render_tree::MapToMeshFilter; +using render_tree::Node; using render_tree::PunchThroughVideoNode; using render_tree::RectNode; using render_tree::SolidColorBrush; -using render_tree::MapToMeshFilter; namespace { @@ -57,6 +60,25 @@ // means, as per https://www.w3.org/TR/CSS21/visudet.html#inline-replaced-width. const float kFallbackWidth = 300.0f; +// Convert the parsed keyword value for a stereo mode into a stereo mode enum +// value. +render_tree::StereoMode ReadStereoMode( + const scoped_refptr<cssom::KeywordValue>& keyword_value) { + cssom::KeywordValue::Value value = keyword_value->value(); + + if (value == cssom::KeywordValue::kMonoscopic) { + return render_tree::kMono; + } else if (value == cssom::KeywordValue::kStereoscopicLeftRight) { + return render_tree::kLeftRight; + } else if (value == cssom::KeywordValue::kStereoscopicTopBottom) { + return render_tree::kTopBottom; + } else { + LOG(DFATAL) << "Stereo mode has an invalid non-NULL value, defaulting to " + << "monoscopic"; + return render_tree::kMono; + } +} + } // namespace ReplacedBox::ReplacedBox( @@ -169,10 +191,10 @@ return GetMarginBoxHeight(); } -namespace { - #if !PUNCH_THROUGH_VIDEO_RENDERING +namespace { + void AddLetterboxFillRects(const LetterboxDimensions& dimensions, CompositionNode::Builder* composition_node_builder) { const render_tree::ColorRGBA kSolidBlack(0, 0, 0, 1); @@ -197,10 +219,7 @@ AddLetterboxFillRects(dimensions, composition_node_builder); } -#endif // !PUNCH_THROUGH_VIDEO_RENDERING - void AnimateCB(const ReplacedBox::ReplaceImageCB& replace_image_cb, - const ReplacedBox::SetBoundsCB& set_bounds_cb, math::SizeF destination_size, CompositionNode::Builder* composition_node_builder, base::TimeDelta time) { @@ -209,14 +228,6 @@ DCHECK(!replace_image_cb.is_null()); DCHECK(composition_node_builder); -#if PUNCH_THROUGH_VIDEO_RENDERING - // For systems that have their own path to blitting video to the display, we - // simply punch a hole through our scene so that the video can appear there. - PunchThroughVideoNode::Builder builder(math::RectF(destination_size), - set_bounds_cb); - composition_node_builder->AddChild(new PunchThroughVideoNode(builder)); -#else // PUNCH_THROUGH_VIDEO_RENDERING - UNREFERENCED_PARAMETER(set_bounds_cb); scoped_refptr<render_tree::Image> image = replace_image_cb.Run(); // TODO: Detect better when the intrinsic video size is used for the @@ -231,11 +242,12 @@ GetLetterboxDimensions(image->GetSize(), destination_size), image, composition_node_builder); } -#endif // PUNCH_THROUGH_VIDEO_RENDERING } } // namespace +#endif // !PUNCH_THROUGH_VIDEO_RENDERING + void ReplacedBox::RenderAndAnimateContent( CompositionNode::Builder* border_node_builder) const { if (computed_style()->visibility() != cssom::KeywordValue::GetVisible()) { @@ -254,14 +266,35 @@ scoped_refptr<CompositionNode> composition_node = new CompositionNode(composition_node_builder); +#if PUNCH_THROUGH_VIDEO_RENDERING + // For systems that have their own path to blitting video to the display, we + // simply punch a hole through our scene so that the video can appear there. + PunchThroughVideoNode::Builder builder(math::RectF(content_box_size()), + set_bounds_cb_); + Node* frame_node = new PunchThroughVideoNode(builder); +#else AnimateNode::Builder animate_node_builder; animate_node_builder.Add( - composition_node, base::Bind(AnimateCB, replace_image_cb_, set_bounds_cb_, + composition_node, base::Bind(AnimateCB, replace_image_cb_, content_box_size())); - // TODO: Apply map to mesh filter if present. - border_node_builder->AddChild( - new AnimateNode(animate_node_builder, composition_node)); + Node* frame_node = new AnimateNode(animate_node_builder, composition_node); +#endif // PUNCH_THROUGH_VIDEO_RENDERING + + const cssom::MTMFunction* mtm_filter_function = + cssom::MTMFunction::ExtractFromFilterList(computed_style()->filter()); + + Node* content_node; + if (mtm_filter_function) { + const scoped_refptr<cssom::KeywordValue>& stereo_mode_keyword_value = + mtm_filter_function->stereo_mode(); + render_tree::StereoMode stereo_mode = + ReadStereoMode(stereo_mode_keyword_value); + content_node = new FilterNode(MapToMeshFilter(stereo_mode), frame_node); + } else { + content_node = frame_node; + } + border_node_builder->AddChild(content_node); } void ReplacedBox::UpdateContentSizeAndMargins(
diff --git a/src/cobalt/layout/used_style.cc b/src/cobalt/layout/used_style.cc index 02227e0..26cc44d 100644 --- a/src/cobalt/layout/used_style.cc +++ b/src/cobalt/layout/used_style.cc
@@ -267,6 +267,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kMonospace: case cssom::KeywordValue::kNone: case cssom::KeywordValue::kNoRepeat: @@ -284,6 +285,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible: @@ -356,6 +359,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kNone: case cssom::KeywordValue::kNoRepeat: case cssom::KeywordValue::kNormal: @@ -370,6 +374,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible: @@ -560,7 +566,8 @@ background_size_(background_size), background_position_(background_position), background_repeat_(background_repeat), - used_style_provider_(used_style_provider) {} + used_style_provider_(used_style_provider), + is_opaque_(false) {} void UsedBackgroundNodeProvider::VisitAbsoluteURL( cssom::AbsoluteURLValue* url_value) { @@ -589,10 +596,16 @@ &used_background_size_provider, &used_background_position_provider, &used_background_repeat_provider, frame_, single_image_size); + math::RectF image_rect(image_transform_data.composition_node_translation, + image_transform_data.image_node_size); + + is_opaque_ = used_background_image->IsOpaque() && + image_rect.x() <= frame_.x() && image_rect.y() <= frame_.y() && + image_rect.right() >= frame_.right() && + image_rect.bottom() >= frame_.bottom(); + background_node_ = new render_tree::ImageNode( - used_background_image, - math::RectF(image_transform_data.composition_node_translation, - image_transform_data.image_node_size), + used_background_image, image_rect, image_transform_data.image_node_transform_matrix); } } @@ -1163,6 +1176,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kMonospace: case cssom::KeywordValue::kNone: case cssom::KeywordValue::kNoRepeat: @@ -1180,6 +1194,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible: @@ -1341,6 +1357,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kMonospace: case cssom::KeywordValue::kNone: case cssom::KeywordValue::kNoRepeat: @@ -1358,6 +1375,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible: @@ -1411,6 +1430,7 @@ case cssom::KeywordValue::kLeft: case cssom::KeywordValue::kLineThrough: case cssom::KeywordValue::kMiddle: + case cssom::KeywordValue::kMonoscopic: case cssom::KeywordValue::kMonospace: case cssom::KeywordValue::kNoRepeat: case cssom::KeywordValue::kNormal: @@ -1427,6 +1447,8 @@ case cssom::KeywordValue::kSolid: case cssom::KeywordValue::kStart: case cssom::KeywordValue::kStatic: + case cssom::KeywordValue::kStereoscopicLeftRight: + case cssom::KeywordValue::kStereoscopicTopBottom: case cssom::KeywordValue::kTop: case cssom::KeywordValue::kUppercase: case cssom::KeywordValue::kVisible:
diff --git a/src/cobalt/layout/used_style.h b/src/cobalt/layout/used_style.h index ca32a1c..8c964a1 100644 --- a/src/cobalt/layout/used_style.h +++ b/src/cobalt/layout/used_style.h
@@ -134,6 +134,8 @@ return background_node_; } + bool is_opaque() const { return is_opaque_; } + private: const math::RectF frame_; const scoped_refptr<cssom::PropertyValue> background_size_; @@ -143,6 +145,8 @@ scoped_refptr<render_tree::Node> background_node_; + bool is_opaque_; + DISALLOW_COPY_AND_ASSIGN(UsedBackgroundNodeProvider); };
diff --git a/src/cobalt/layout_tests/layout_benchmarks.cc b/src/cobalt/layout_tests/layout_benchmarks.cc index 164741c..bace1ed 100644 --- a/src/cobalt/layout_tests/layout_benchmarks.cc +++ b/src/cobalt/layout_tests/layout_benchmarks.cc
@@ -45,14 +45,18 @@ RendererBenchmarkRunner() : done_gathering_samples_(true, false), system_window_(system_window::CreateSystemWindow( - &event_dispatcher_, math::Size(kViewportWidth, kViewportHeight))), - renderer_module_(system_window_.get(), - renderer::RendererModule::Options()) {} + &event_dispatcher_, math::Size(kViewportWidth, kViewportHeight))) { + // Since we'd like to measure the renderer, we force it to rasterize each + // frame despite the fact that the render tree may not be changing. + renderer::RendererModule::Options renderer_options; + renderer_options.submit_even_if_render_tree_is_unchanged = true; + renderer_module_.emplace(system_window_.get(), renderer_options); + } // Return the resource provider from the internal renderer so that it can // be used during layout. render_tree::ResourceProvider* GetResourceProvider() { - return renderer_module_.pipeline()->GetResourceProvider(); + return renderer_module_->pipeline()->GetResourceProvider(); } // Run the renderer benchmarks and perform the measurements. @@ -67,11 +71,11 @@ submission_with_callback.on_rasterized_callback = base::Bind( &RendererBenchmarkRunner::OnSubmitComplete, base::Unretained(this)); - renderer_module_.pipeline()->Submit(submission_with_callback); + renderer_module_->pipeline()->Submit(submission_with_callback); done_gathering_samples_.Wait(); - renderer_module_.pipeline()->Clear(); + renderer_module_->pipeline()->Clear(); } private: @@ -93,7 +97,7 @@ base::EventDispatcher event_dispatcher_; scoped_ptr<system_window::SystemWindow> system_window_; - renderer::RendererModule renderer_module_; + base::optional<renderer::RendererModule> renderer_module_; }; } // namespace
diff --git a/src/cobalt/layout_tests/layout_tests.cc b/src/cobalt/layout_tests/layout_tests.cc index c2e63c0..38adb86 100644 --- a/src/cobalt/layout_tests/layout_tests.cc +++ b/src/cobalt/layout_tests/layout_tests.cc
@@ -92,7 +92,7 @@ scoped_refptr<render_tree::animations::AnimateNode> animate_node = new render_tree::animations::AnimateNode(layout_results.render_tree); scoped_refptr<render_tree::Node> animated_tree = - animate_node->Apply(layout_results.layout_time); + animate_node->Apply(layout_results.layout_time).animated; bool results = pixel_tester.TestTree(animated_tree, GetParam().base_file_path); @@ -189,6 +189,11 @@ AnimationTimingAPILayoutTests, LayoutTest, ::testing::ValuesIn(EnumerateLayoutTests("animation-timing"))); +// Problematic test cases found through cluster-fuzz. +INSTANTIATE_TEST_CASE_P( + ClusterFuzzLayoutTests, LayoutTest, + ::testing::ValuesIn(EnumerateLayoutTests("cluster-fuzz"))); + // Disable on Windows until network stack is implemented. #if !defined(COBALT_WIN) // Content Security Policy test cases.
diff --git a/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222-expected.png b/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222-expected.png new file mode 100644 index 0000000..f1b767f --- /dev/null +++ b/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222.html b/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222.html new file mode 100644 index 0000000..9f6ed1a --- /dev/null +++ b/src/cobalt/layout_tests/testdata/cluster-fuzz/fuzz-222.html
@@ -0,0 +1,17 @@ +cm/000000086.jpg" target="_blank"><img src="./picm/000000086.jpg" width="277" style="border: 0px solid ;"></a> +<div class="blockImageCaption"><br>�s�d�k�F�i�O�W�V�j�W�V�W�|�Q�P�W�W�i���j<br>�e�`�w�F�i�O�W�V�j�W�V�W�|�Q�V�V�P</p> +<!-- --> +<!-- /blockText --></div> +<!-- --> +<!-- /blockText --></div> +<!-- /block2column2 --></div> +<br class="blockEdge"> +<!-- /CB0200_4 --></div> +> +<!-- --> + +<!-- --><div class="blockImage"> +<!-- --> + class="blockImageCaption"><small></small></div> +> +<!-- \ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/cluster-fuzz/layout_tests.txt b/src/cobalt/layout_tests/testdata/cluster-fuzz/layout_tests.txt new file mode 100644 index 0000000..12a4567 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/cluster-fuzz/layout_tests.txt
@@ -0,0 +1 @@ +fuzz-222 \ No newline at end of file
diff --git a/src/cobalt/layout_tests/testdata/cobalt/100-dynamically-created-nested-elements.html b/src/cobalt/layout_tests/testdata/cobalt/100-dynamically-created-nested-elements.html index 498e1a7..2c692ea 100644 --- a/src/cobalt/layout_tests/testdata/cobalt/100-dynamically-created-nested-elements.html +++ b/src/cobalt/layout_tests/testdata/cobalt/100-dynamically-created-nested-elements.html
@@ -16,12 +16,12 @@ that max element depth is 32. --> <script> - var parent = document.body; + var parent_element = document.body; for (var i = 1; i <= 100; i++) { var new_child = document.createElement("span"); new_child.textContent = i + " "; - parent.appendChild(new_child); - parent = new_child; + parent_element.appendChild(new_child); + parent_element = new_child; } </script>
diff --git a/src/cobalt/layout_tests/testdata/cobalt/image-from-blob-expected.png b/src/cobalt/layout_tests/testdata/cobalt/image-from-blob-expected.png new file mode 100644 index 0000000..7c0e8e1 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/cobalt/image-from-blob-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/cobalt/image-from-blob.html b/src/cobalt/layout_tests/testdata/cobalt/image-from-blob.html new file mode 100644 index 0000000..aa33681 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/cobalt/image-from-blob.html
@@ -0,0 +1,57 @@ +<!DOCTYPE html> +<!-- + | Loading an image from a Blob created from a buffer in memory. + --> +<html> +<head> + <style> + div { + width: 500px; + height: 400px; + } + </style> +</head> +<body> + <div id='image'></div> + <script> + if (window.testRunner) { + window.testRunner.waitUntilDone(); + } + + var image_bytes = [ + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, + 6, 0, 0, 0, 6, 8, 2, 0, 0, 0, 111, 174, 120, 31, 0, 0, 0, 3, 115, 66, + 73, 84, 8, 8, 8, 219, 225, 79, 224, 0, 0, 0, 25, 116, 69, 88, 116, 83, + 111, 102, 116, 119, 97, 114, 101, 0, 103, 110, 111, 109, 101, 45, 115, + 99, 114, 101, 101, 110, 115, 104, 111, 116, 239, 3, 191, 62, 0, 0, 0, + 36, 73, 68, 65, 84, 8, 153, 99, 252, 80, 238, 198, 128, 10, 88, 24, 24, + 24, 154, 119, 190, 132, 243, 107, 221, 197, 153, 24, 48, 0, 113, 66, + 140, 152, 198, 3, 0, 225, 21, 6, 103, 179, 203, 171, 64, 0, 0, 0, 0, + 73, 69, 78, 68, 174, 66, 96, 130]; + + // TODO(jsalvadorp): When TypedArray supports setting from a Javascript + // array, replace the following loop. + var image_array = new Uint8Array(image_bytes.length); + for (var i = 0; i < image_array.length; i++) { + image_array[i] = image_bytes[i]; + } + + // TODO(jsalvadorp): When the proper Blob constructor that takes an array + // of arguments is supported, change the following to reflect the new + // signature. + var image_blob = new Blob(image_array.buffer); + var image_url = URL.createObjectURL(image_blob); + + var image = new Image(); + image.onload = function() { + var cobalt = document.getElementById('image'); + cobalt.style.background = 'url(' + image_url + ')'; + + if (window.testRunner) { + window.testRunner.notifyDone(); + } + } + image.src = image_url; + </script> +</body> +</html>
diff --git a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt index e336781..03292a0 100644 --- a/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt +++ b/src/cobalt/layout_tests/testdata/cobalt/layout_tests.txt
@@ -8,6 +8,7 @@ divs-with-background-color-and-text fixed-width-divs-with-background-color font-weight +image-from-blob inline-box-with-overflow-words inline-style-allowed-while-cloning-objects onload_event_fired_even_though_link_file_does_not_exist
diff --git a/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color-expected.png b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color-expected.png new file mode 100644 index 0000000..6418185 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color.html b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color.html new file mode 100644 index 0000000..49defbc --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-background/14-2-1-background-with-opaque-image-color.html
@@ -0,0 +1,49 @@ +<!DOCTYPE html> +<!-- + | Tests that opaque images render correctly and exercises an optimization + | code path where a background color does not need to be rendered if an + | opaque image covers an element's frame instead. + --> +<html> +<head> + <style> + div { + width: 300px; + height: 80px; + background-color: #f88; + background-repeat: no-repeat; + } + </style> + <script> + if (window.testRunner) { + window.testRunner.waitUntilDone(); + } + + var image = new Image(); + var image_name = 'cobalt_opaque.jpg'; + + image.onload = function() { + var image_elements = document.getElementsByClassName('image'); + for (var i = 0; i < image_elements.length; ++i) { + image_elements[i].style.backgroundImage = + 'url(' + image_name + ')'; + } + + if (window.testRunner) { + window.testRunner.notifyDone(); + } + } + + image.src = image_name; + +</script> +</head> +<body> + <div class='image' style="background-repeat: repeat;"></div> + <div class='image' style="width: 100px; background-size: cover;"></div> + <div class='image' style="background-size: contain;"></div> + <!-- Matches the image's dimensions exactly. --> + <div class='image' + style="width: 87px; height: 100px; background-size: contain;"></div> +</body> +</html>
diff --git a/src/cobalt/layout_tests/testdata/css3-background/cobalt_opaque.jpg b/src/cobalt/layout_tests/testdata/css3-background/cobalt_opaque.jpg new file mode 100644 index 0000000..a1dc59e --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-background/cobalt_opaque.jpg Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-background/layout_tests.txt b/src/cobalt/layout_tests/testdata/css3-background/layout_tests.txt index e5e5ccb..45cffd4 100644 --- a/src/cobalt/layout_tests/testdata/css3-background/layout_tests.txt +++ b/src/cobalt/layout_tests/testdata/css3-background/layout_tests.txt
@@ -27,6 +27,7 @@ 14-2-1-background-with-image-repeat-color-position 14-2-1-background-with-image-repeat-position 14-2-1-background-with-image-repeat-position-color +14-2-1-background-with-opaque-image-color 14-2-1-background-with-position-color 14-2-1-background-with-position-color-image 14-2-1-background-with-position-color-image-repeat
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-2-font-face-font-family-hides-system-font-family.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-2-font-face-font-family-hides-system-font-family.html index ce75b15..d50693a 100644 --- a/src/cobalt/layout_tests/testdata/css3-fonts/4-2-font-face-font-family-hides-system-font-family.html +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-2-font-face-font-family-hides-system-font-family.html
@@ -34,11 +34,12 @@ } @font-face { font-family: Roboto; - src: local(cursive); + src: local(DancingScript); /* Try using a Postscript name of a typeface. + */ } @font-face { font-family: serif; - src: local(casual); + src: local(Coming Soon); /* Try using a Full Name of a typeface. */ } </style> </head>
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-first-available-local-font-face.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-first-available-local-font-face.html index 18b941d..0d87b10 100644 --- a/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-first-available-local-font-face.html +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-first-available-local-font-face.html
@@ -37,25 +37,25 @@ } @font-face { font-family: font-face-1; - src: local(cursive); + src: local(DancingScript); /* Lookup using font's postscript name. */ } @font-face { font-family: font-face-2; src: local(invalid1), - local(casual); + local(Coming Soon); /* Lookup using font's full name. */ } @font-face { font-family: font-face-3; src: local(invalid1), local(invalid2), - local(sans-serif-smallcaps); + local(CarroisGothicSC-Regular); } @font-face { font-family: font-face-4; src: local(invalid1), - local(cursive), + local(DancingScript), /* Looking using font's postscript name. */ local(invalid2), - local(casual), + local(Coming Soon), /* Lookup using font's full name. */ local(invalid3); } </style>
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html index 593ccdf..fe1ac19 100644 --- a/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-3-use-next-font-family-if-font-face-sources-unavailable.html
@@ -39,12 +39,12 @@ } @font-face { font-family: font-face-1; - src: local(cursive); + src: local(DancingScript); /* Lookup using font's postscript name. */ } @font-face { font-family: font-face-2; src: local(invalid1), - local(casual); + local(Coming Soon); /* Lookup using font's full name. */ } @font-face { font-family: font-face-3; @@ -55,9 +55,9 @@ @font-face { font-family: font-face-4; src: local(invalid1), - local(cursive), + local(DancingScript), /* Lookup using font's postscript name. */ local(invalid2), - local(casual), + local(Coming Soon), /* Lookup using font's full name. */ local(invalid3); } </style>
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png b/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png index 0e42727..e894c76 100644 --- a/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set.html b/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set.html index 74a3b50..b9bd6d1 100644 --- a/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set.html +++ b/src/cobalt/layout_tests/testdata/css3-fonts/4-4-use-correct-style-in-font-face-set.html
@@ -67,29 +67,29 @@ } @font-face { font-family: font-face-1; - src: local(Roboto); + src: local(Roboto-Regular); } @font-face { font-family: font-face-2; - src: local(cursive); + src: local(DancingScript); font-weight: normal; font-style: normal; } @font-face { font-family: font-face-2; - src: local(casual); + src: local(Coming Soon); font-weight: normal; font-style: italic; } @font-face { font-family: font-face-2; - src: local(sans-serif-smallcaps); + src: local(CarroisGothicSC-Regular); font-weight: bold; font-style: normal; } @font-face { font-family: font-face-2; - src: local(monospace); + src: local(Droid Sans Mono); font-weight: bold; font-style: italic; } @@ -98,9 +98,13 @@ <body> <div class="containing-block"> <div class="font-face-1-normal-normal">The Hegemony Consul</div> + <!-- Note: font-face-1 selects a local font file, which only has an regular + style. So, for the next three cases, a normal style is rendered. + --> <div class="font-face-1-normal-italic">The Hegemony Consul</div> <div class="font-face-1-bold-normal">The Hegemony Consul</div> <div class="font-face-1-bold-italic">The Hegemony Consul</div> + <div class="font-face-2-normal-normal">The Hegemony Consul</div> <div class="font-face-2-normal-italic">The Hegemony Consul</div> <div class="font-face-2-bold-normal">The Hegemony Consul</div>
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle-expected.png new file mode 100644 index 0000000..39205b9 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle.html new file mode 100644 index 0000000..4972e3d --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-gradient-in-middle.html
@@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <style> + div { + background: linear-gradient(to left, yellow 20%, purple 70%); + width: 100px; + height: 100px; + } + </style> +</head> +<body> + <div></div> +</body> +</html> +
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-expected.png new file mode 100644 index 0000000..c2bdcce --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient-expected.png new file mode 100644 index 0000000..c2bdcce --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient.html new file mode 100644 index 0000000..03019b7 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-multistop-one-dimensional-gradient.html
@@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <style> + div { + background: linear-gradient(to left, yellow, purple, green); + width: 100px; + height: 100px; + } + </style> +</head> +<body> + <div></div> +</body> +</html> +
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient-expected.png new file mode 100644 index 0000000..ac7cae8 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient.html new file mode 100644 index 0000000..36cdbcb --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-bottom-linear-gradient.html
@@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <style> + div { + background: linear-gradient(to bottom, yellow, transparent); + width: 100px; + height: 100px; + } + </style> +</head> +<body> + <div></div> +</body> +</html> +
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient-expected.png new file mode 100644 index 0000000..996008f --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient.html new file mode 100644 index 0000000..b161dbd --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-left-linear-gradient.html
@@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <style> + div { + background: linear-gradient(to left, yellow, transparent); + width: 100px; + height: 100px; + } + </style> +</head> +<body> + <div></div> +</body> +</html> +
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient-expected.png new file mode 100644 index 0000000..c96bfe7 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient.html new file mode 100644 index 0000000..e56d490 --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-right-linear-gradient.html
@@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <style> + div { + background: linear-gradient(to right, yellow, transparent); + width: 100px; + height: 100px; + } + </style> +</head> +<body> + <div></div> +</body> +</html> +
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient-expected.png b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient-expected.png new file mode 100644 index 0000000..e4ec68c --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient-expected.png Binary files differ
diff --git a/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient.html b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient.html new file mode 100644 index 0000000..683c1da --- /dev/null +++ b/src/cobalt/layout_tests/testdata/css3-images/4-1-2-to-top-linear-gradient.html
@@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html> +<head> + <style> + div { + background: linear-gradient(to top, yellow, transparent); + width: 100px; + height: 100px; + } + </style> +</head> +<body> + <div></div> +</body> +</html> +
diff --git a/src/cobalt/layout_tests/testdata/css3-images/layout_tests.txt b/src/cobalt/layout_tests/testdata/css3-images/layout_tests.txt index d2988a2..6abfa12 100644 --- a/src/cobalt/layout_tests/testdata/css3-images/layout_tests.txt +++ b/src/cobalt/layout_tests/testdata/css3-images/layout_tests.txt
@@ -1,9 +1,15 @@ 4-1-2-corner-to-corner-linear-gradient +4-1-2-gradient-in-middle 4-1-2-linear-gradient-with-same-stop-positions 4-1-2-linear-gradient-with-transparency +4-1-2-multistop-one-dimensional-gradient 4-1-2-simple-yellow-to-blue-arbitrary-angle-linear-gradient 4-1-2-simple-yellow-to-blue-vertical-linear-gradient 4-1-2-three-color-linear-gradient +4-1-2-to-bottom-linear-gradient +4-1-2-to-left-linear-gradient +4-1-2-to-right-linear-gradient +4-1-2-to-top-linear-gradient 4-2-4-closest-side-radial-gradient 4-2-4-radial-gradient-non-centered 4-2-4-radial-gradient-with-transparency
diff --git a/src/cobalt/loader/blob_fetcher.cc b/src/cobalt/loader/blob_fetcher.cc new file mode 100644 index 0000000..d6a48b8 --- /dev/null +++ b/src/cobalt/loader/blob_fetcher.cc
@@ -0,0 +1,54 @@ +/* + * Copyright 2016 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/loader/blob_fetcher.h" + +#include "base/bind.h" +#include "base/message_loop.h" + +namespace cobalt { +namespace loader { + +BlobFetcher::BlobFetcher(const GURL& url, Handler* handler, + const ResolverCallback& resolver_callback) + : Fetcher(handler), + url_(url), + resolver_callback_(resolver_callback), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { + DCHECK(!resolver_callback_.is_null()); + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&BlobFetcher::Fetch, weak_ptr_factory_.GetWeakPtr())); +} + +void BlobFetcher::Fetch() { + size_t buffer_size = 0; + const char* buffer_data = NULL; + + if (resolver_callback_.Run(url_, &buffer_data, &buffer_size)) { + if (buffer_size > 0) { + handler()->OnReceived(this, buffer_data, buffer_size); + } + handler()->OnDone(this); + } else { + handler()->OnError(this, "Blob URL not found in object store."); + } +} + +BlobFetcher::~BlobFetcher() {} + +} // namespace loader +} // namespace cobalt
diff --git a/src/cobalt/loader/blob_fetcher.h b/src/cobalt/loader/blob_fetcher.h new file mode 100644 index 0000000..9b40c3f --- /dev/null +++ b/src/cobalt/loader/blob_fetcher.h
@@ -0,0 +1,61 @@ +/* + * Copyright 2016 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_LOADER_BLOB_FETCHER_H_ +#define COBALT_LOADER_BLOB_FETCHER_H_ + +#include "base/memory/weak_ptr.h" +#include "cobalt/loader/fetcher.h" + +namespace cobalt { +namespace loader { + +// For fetching the 'blob:' scheme. +class BlobFetcher : public Fetcher { + public: + // This callback avoids a dependency from the fetcher to the actual blob + // implementation. + // If the blob is succesfully fetched: + // 1. Writes the size of its buffer to |size|. + // 2. If |size| > 0, then it also writes the address of the non-empty buffer + // to |data|, otherwise writes NULL to |data|. + // 3. Returns true. + // If the fetch fails: + // 1. Writes 0 to |size| + // 2. Writes NULL to |data| + // 3. Returns false. + typedef base::Callback<bool(const GURL& url, const char** data, size_t* size)> + ResolverCallback; + + explicit BlobFetcher(const GURL& url, Handler* handler, + const ResolverCallback& resolver_callback); + + void Fetch(); + + ~BlobFetcher() OVERRIDE; + + private: + void GetData(); + + GURL url_; + ResolverCallback resolver_callback_; + base::WeakPtrFactory<BlobFetcher> weak_ptr_factory_; +}; + +} // namespace loader +} // namespace cobalt + +#endif // COBALT_LOADER_BLOB_FETCHER_H_
diff --git a/src/cobalt/loader/blob_fetcher_test.cc b/src/cobalt/loader/blob_fetcher_test.cc new file mode 100644 index 0000000..e2baea3 --- /dev/null +++ b/src/cobalt/loader/blob_fetcher_test.cc
@@ -0,0 +1,140 @@ +/* + * Copyright 2016 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/loader/blob_fetcher.h" + +#include <map> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/run_loop.h" +#include "cobalt/loader/fetcher_test.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::AtLeast; +using ::testing::InSequence; +using ::testing::StrictMock; +using ::testing::_; + +namespace cobalt { +namespace loader { +namespace { + +typedef std::map<std::string, std::vector<char> > TestRegistry; + +bool TestResolver(const TestRegistry& registry, const GURL& url, + const char** data, size_t* size) { + DCHECK(data); + DCHECK(size); + + *size = 0; + *data = NULL; + + TestRegistry::const_iterator match = registry.find(url.spec()); + if (match != registry.end()) { + const std::vector<char>& buffer = match->second; + *size = buffer.size(); + + if (!buffer.empty()) { + *data = &buffer[0]; + } + + return true; + } else { + return false; + } +} + +TEST(BlobFetcherTest, NonExistentBlobURL) { + MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + base::RunLoop run_loop; + + StrictMock<MockFetcherHandler> fetcher_handler_mock(&run_loop); + EXPECT_CALL(fetcher_handler_mock, OnError(_, _)); + + TestRegistry registry; + + scoped_ptr<BlobFetcher> blob_fetcher = make_scoped_ptr( + new loader::BlobFetcher(GURL("blob:sd98sdfuh8sdh"), &fetcher_handler_mock, + base::Bind(&TestResolver, registry))); + + run_loop.Run(); + + EXPECT_EQ(blob_fetcher.get(), fetcher_handler_mock.fetcher()); +} + +TEST(BlobFetcherTest, EmptyBlob) { + MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + base::RunLoop run_loop; + InSequence dummy; + + StrictMock<MockFetcherHandler> fetcher_handler_mock(&run_loop); + EXPECT_CALL(fetcher_handler_mock, OnDone(_)); + + const char* url = "blob:28y3-fsdaf-dsfa"; + + TestRegistry registry; + registry[url]; + + scoped_ptr<BlobFetcher> blob_fetcher = make_scoped_ptr( + new loader::BlobFetcher(GURL(url), &fetcher_handler_mock, + base::Bind(&TestResolver, registry))); + + run_loop.Run(); + + EXPECT_EQ(0, fetcher_handler_mock.data().size()); + + EXPECT_EQ(blob_fetcher.get(), fetcher_handler_mock.fetcher()); +} + +TEST(BlobFetcherTest, ValidBlob) { + MessageLoop message_loop(MessageLoop::TYPE_DEFAULT); + base::RunLoop run_loop; + InSequence dummy; + + StrictMock<MockFetcherHandler> fetcher_handler_mock(&run_loop); + EXPECT_CALL(fetcher_handler_mock, OnReceived(_, _, _)).Times(AtLeast(1)); + EXPECT_CALL(fetcher_handler_mock, OnDone(_)); + + const char* url = "blob:28y3-fsdaf-dsfa"; + + TestRegistry registry; + std::vector<char>& buffer = registry[url]; + buffer.push_back('a'); + buffer.push_back(0); + buffer.push_back(7); + + scoped_ptr<BlobFetcher> blob_fetcher = make_scoped_ptr( + new loader::BlobFetcher(GURL(url), &fetcher_handler_mock, + base::Bind(&TestResolver, registry))); + + run_loop.Run(); + + const std::string& data = fetcher_handler_mock.data(); + ASSERT_EQ(3, data.size()); + EXPECT_EQ('a', data[0]); + EXPECT_EQ(0, data[1]); + EXPECT_EQ(7, data[2]); + + EXPECT_EQ(blob_fetcher.get(), fetcher_handler_mock.fetcher()); +} + +} // namespace +} // namespace loader +} // namespace cobalt
diff --git a/src/cobalt/loader/decoder.h b/src/cobalt/loader/decoder.h index bdd4040..2a0adcb 100644 --- a/src/cobalt/loader/decoder.h +++ b/src/cobalt/loader/decoder.h
@@ -17,6 +17,9 @@ #ifndef COBALT_LOADER_DECODER_H_ #define COBALT_LOADER_DECODER_H_ +#include <string> + +#include "base/memory/scoped_ptr.h" #include "cobalt/loader/loader_types.h" #include "cobalt/render_tree/resource_provider.h" #include "net/http/http_response_headers.h" @@ -43,6 +46,16 @@ // This is the interface that chunks of bytes can be sent in to be decoded. virtual void DecodeChunk(const char* data, size_t size) = 0; + // A decoder can choose to implement |DecodeChunkPassed| in order to take + // ownership of the chunk data. Taking ownership over the chunk data can + // allow data copies to be avoided, such as when passing the data off to be + // decoded asynchronously. Not all fetchers are guaranteed to support this + // though, in which case they simply forward the scoped pointer data to + // DecodeChunk. + virtual void DecodeChunkPassed(scoped_ptr<std::string> data) { + DecodeChunk(data->data(), data->size()); + } + // This is called when all data are sent in and decoding should be finalized. virtual void Finish() = 0;
diff --git a/src/cobalt/loader/embedded_resources/splash_screen.css b/src/cobalt/loader/embedded_resources/splash_screen.css index 0af40bf..3cba6e4 100644 --- a/src/cobalt/loader/embedded_resources/splash_screen.css +++ b/src/cobalt/loader/embedded_resources/splash_screen.css
@@ -4,11 +4,11 @@ } #splash { - background-color: #e62d27; + background-color: #e62117; background-image: url("h5vcc-embedded://you_tube_logo.png"); background-position: center center; background-repeat: no-repeat; - background-size: 60%; + background-size: 100%; height: 100%; left: 0; position: absolute; @@ -22,13 +22,12 @@ width: 100%; } -@keyframes loadwait { - 0% {opacity: 0} - 100% {opacity: 1} -} - #spinner { - animation: loadwait 2s steps(1, end); + /* The spinner starts with display set to none, and JavaScript will set this + to 'block' after some time has passed, if the splash screen is still + visible. */ + display: none; + height: 5.33em; margin: 0 auto; position: relative;
diff --git a/src/cobalt/loader/embedded_resources/splash_screen.html b/src/cobalt/loader/embedded_resources/splash_screen.html index 2a572ed..0e57e3b 100644 --- a/src/cobalt/loader/embedded_resources/splash_screen.html +++ b/src/cobalt/loader/embedded_resources/splash_screen.html
@@ -3,8 +3,9 @@ <head> <meta http-equiv="Content-Security-Policy" content="default-src 'none'; + script-src h5vcc-embedded://*/splash_screen.js; style-src h5vcc-embedded://*/splash_screen.css; - img-src h5vcc-embedded://*/you_tube_logo.png"> + img-src h5vcc-embedded://*/you_tube_logo.png;"> <link rel="stylesheet" type="text/css" href="h5vcc-embedded://splash_screen.css"> </head> @@ -24,6 +25,8 @@ <div class="dot" id="dot8"></div> </div> </div> + <script type="text/javascript" src="h5vcc-embedded://splash_screen.js"> + </script> </body> </html>
diff --git a/src/cobalt/loader/embedded_resources/splash_screen.js b/src/cobalt/loader/embedded_resources/splash_screen.js new file mode 100644 index 0000000..0d30edc --- /dev/null +++ b/src/cobalt/loader/embedded_resources/splash_screen.js
@@ -0,0 +1,5 @@ + // Enable the spinner after 3 seconds have passed. + window.setTimeout(function() { + var spinner = document.getElementById('spinner'); + spinner.style.display = 'block'; + }, 3000);
diff --git a/src/cobalt/loader/embedded_resources/you_tube_logo.png b/src/cobalt/loader/embedded_resources/you_tube_logo.png index 0d2082d..a964097 100644 --- a/src/cobalt/loader/embedded_resources/you_tube_logo.png +++ b/src/cobalt/loader/embedded_resources/you_tube_logo.png Binary files differ
diff --git a/src/cobalt/loader/fetcher.h b/src/cobalt/loader/fetcher.h index 4ca6d3f..e998f44 100644 --- a/src/cobalt/loader/fetcher.h +++ b/src/cobalt/loader/fetcher.h
@@ -46,6 +46,14 @@ virtual void OnDone(Fetcher* fetcher) = 0; virtual void OnError(Fetcher* fetcher, const std::string& error) = 0; + // By default, |OnReceivedPassed| forwards the scoped_ptr<std::string> + // data into |OnReceived|. Implementations have the opportunity to hold + // onto the scoped_ptr through overriding |OnReceivedPassed|. + virtual void OnReceivedPassed(Fetcher* fetcher, + scoped_ptr<std::string> data) { + OnReceived(fetcher, data->data(), data->length()); + } + protected: Handler() {} virtual ~Handler() {}
diff --git a/src/cobalt/loader/fetcher_factory.cc b/src/cobalt/loader/fetcher_factory.cc index e16c970..6b478e3 100644 --- a/src/cobalt/loader/fetcher_factory.cc +++ b/src/cobalt/loader/fetcher_factory.cc
@@ -23,6 +23,7 @@ #include "base/logging.h" #include "base/path_service.h" #include "cobalt/loader/about_fetcher.h" +#include "cobalt/loader/blob_fetcher.h" #include "cobalt/loader/embedded_fetcher.h" #include "cobalt/loader/file_fetcher.h" #include "cobalt/loader/net_fetcher.h" @@ -69,6 +70,16 @@ file_thread_.Start(); } +FetcherFactory::FetcherFactory( + network::NetworkModule* network_module, const FilePath& extra_search_dir, + const BlobFetcher::ResolverCallback& blob_resolver) + : file_thread_("File"), + network_module_(network_module), + extra_search_dir_(extra_search_dir), + blob_resolver_(blob_resolver) { + file_thread_.Start(); +} + scoped_ptr<Fetcher> FetcherFactory::CreateFetcher(const GURL& url, Fetcher::Handler* handler) { return CreateSecureFetcher(url, csp::SecurityCallback(), handler).Pass(); @@ -104,7 +115,15 @@ fetcher.reset(new AboutFetcher(handler)); } #endif - else { // NOLINT(readability/braces) + else if (url.SchemeIs("blob")) { // NOLINT(readability/braces) + if (!blob_resolver_.is_null()) { + fetcher.reset(new BlobFetcher(url, handler, blob_resolver_)); + } else { + LOG(ERROR) << "Fetcher factory not provided the blob registry, " + "could not fetch the URL: " + << url; + } + } else { // NOLINT(readability/braces) DCHECK(network_module_) << "Network module required."; NetFetcher::Options options; fetcher.reset(new NetFetcher(url, url_security_callback, handler,
diff --git a/src/cobalt/loader/fetcher_factory.h b/src/cobalt/loader/fetcher_factory.h index 3dd64a5..e4f5661 100644 --- a/src/cobalt/loader/fetcher_factory.h +++ b/src/cobalt/loader/fetcher_factory.h
@@ -18,8 +18,10 @@ #define COBALT_LOADER_FETCHER_FACTORY_H_ #include "base/file_path.h" +#include "base/optional.h" #include "base/threading/thread.h" #include "cobalt/csp/content_security_policy.h" +#include "cobalt/loader/blob_fetcher.h" #include "cobalt/loader/fetcher.h" #include "googleurl/src/gurl.h" @@ -35,6 +37,9 @@ explicit FetcherFactory(network::NetworkModule* network_module); FetcherFactory(network::NetworkModule* network_module, const FilePath& extra_search_dir); + FetcherFactory(network::NetworkModule* network_module, + const FilePath& extra_search_dir, + const BlobFetcher::ResolverCallback& blob_resolver); // Creates a fetcher. Returns NULL if the creation fails. scoped_ptr<Fetcher> CreateFetcher(const GURL& url, Fetcher::Handler* handler); @@ -48,6 +53,7 @@ base::Thread file_thread_; network::NetworkModule* network_module_; FilePath extra_search_dir_; + BlobFetcher::ResolverCallback blob_resolver_; }; } // namespace loader
diff --git a/src/cobalt/loader/fetcher_test.h b/src/cobalt/loader/fetcher_test.h new file mode 100644 index 0000000..0b92378 --- /dev/null +++ b/src/cobalt/loader/fetcher_test.h
@@ -0,0 +1,100 @@ +/* + * Copyright 2016 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_LOADER_FETCHER_TEST_H_ +#define COBALT_LOADER_FETCHER_TEST_H_ + +#include "cobalt/loader/fetcher.h" + +#include <string> + +#include "base/message_loop.h" +#include "base/run_loop.h" +#include "testing/gmock/include/gmock/gmock.h" + +using ::testing::Invoke; +using ::testing::_; + +namespace cobalt { +namespace loader { + +class FetcherHandlerForTest : public Fetcher::Handler { + public: + explicit FetcherHandlerForTest(base::RunLoop* run_loop) + : fetcher_(NULL), run_loop_(run_loop) {} + + // From Fetcher::Handler. + void OnReceived(Fetcher* fetcher, const char* data, size_t size) OVERRIDE { + CheckFetcher(fetcher); + data_.append(data, size); + } + void OnDone(Fetcher* fetcher) OVERRIDE { + CheckFetcher(fetcher); + MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure()); + } + void OnError(Fetcher* fetcher, const std::string& error) OVERRIDE { + UNREFERENCED_PARAMETER(error); + CheckFetcher(fetcher); + MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure()); + } + + const std::string& data() const { return data_; } + Fetcher* fetcher() const { return fetcher_; } + + private: + void CheckFetcher(Fetcher* fetcher) { + EXPECT_TRUE(fetcher); + if (fetcher_ == NULL) { + fetcher_ = fetcher; + return; + } + EXPECT_EQ(fetcher_, fetcher); + } + + std::string data_; + Fetcher* fetcher_; + base::RunLoop* run_loop_; +}; + +class MockFetcherHandler : public Fetcher::Handler { + public: + explicit MockFetcherHandler(base::RunLoop* run_loop) : real_(run_loop) { + ON_CALL(*this, OnReceived(_, _, _)) + .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnReceived)); + ON_CALL(*this, OnDone(_)) + .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnDone)); + ON_CALL(*this, OnError(_, _)) + .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnError)); + } + + MOCK_METHOD2(OnResponseStarted, + LoadResponseType( + Fetcher*, const scoped_refptr<net::HttpResponseHeaders>&)); + MOCK_METHOD3(OnReceived, void(Fetcher*, const char*, size_t)); + MOCK_METHOD1(OnDone, void(Fetcher*)); + MOCK_METHOD2(OnError, void(Fetcher*, const std::string&)); + + const std::string& data() const { return real_.data(); } + Fetcher* fetcher() const { return real_.fetcher(); } + + private: + FetcherHandlerForTest real_; +}; + +} // namespace loader +} // namespace cobalt + +#endif // COBALT_LOADER_FETCHER_TEST_H_
diff --git a/src/cobalt/loader/file_fetcher.cc b/src/cobalt/loader/file_fetcher.cc index 1cbe61f..83ee7a6 100644 --- a/src/cobalt/loader/file_fetcher.cc +++ b/src/cobalt/loader/file_fetcher.cc
@@ -40,7 +40,7 @@ // Ensure the request does not attempt to navigate outside the whitelisted // directory. - if (file_path_.ReferencesParent()) { + if (file_path_.ReferencesParent() || file_path_.IsAbsolute()) { handler->OnError(this, PlatformFileErrorToString( base::PLATFORM_FILE_ERROR_ACCESS_DENIED)); return;
diff --git a/src/cobalt/loader/file_fetcher_test.cc b/src/cobalt/loader/file_fetcher_test.cc index 932584a..0b22f71 100644 --- a/src/cobalt/loader/file_fetcher_test.cc +++ b/src/cobalt/loader/file_fetcher_test.cc
@@ -22,83 +22,17 @@ #include "base/message_loop.h" #include "base/path_service.h" #include "base/run_loop.h" +#include "cobalt/loader/fetcher_test.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using ::testing::AtLeast; using ::testing::InSequence; -using ::testing::Invoke; using ::testing::StrictMock; using ::testing::_; namespace cobalt { namespace loader { -namespace { - -class FetcherHandlerForTest : public Fetcher::Handler { - public: - explicit FetcherHandlerForTest(base::RunLoop* run_loop) - : fetcher_(NULL), run_loop_(run_loop) {} - - // From Fetcher::Handler. - void OnReceived(Fetcher* fetcher, const char* data, size_t size) OVERRIDE { - CheckFetcher(fetcher); - data_.append(data, size); - } - void OnDone(Fetcher* fetcher) OVERRIDE { - CheckFetcher(fetcher); - MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure()); - } - void OnError(Fetcher* fetcher, const std::string& error) OVERRIDE { - UNREFERENCED_PARAMETER(error); - CheckFetcher(fetcher); - MessageLoop::current()->PostTask(FROM_HERE, run_loop_->QuitClosure()); - } - - const std::string& data() const { return data_; } - Fetcher* fetcher() const { return fetcher_; } - - private: - void CheckFetcher(Fetcher* fetcher) { - EXPECT_TRUE(fetcher); - if (fetcher_ == NULL) { - fetcher_ = fetcher; - return; - } - EXPECT_EQ(fetcher_, fetcher); - } - - std::string data_; - Fetcher* fetcher_; - base::RunLoop* run_loop_; -}; - -class MockFetcherHandler : public Fetcher::Handler { - public: - explicit MockFetcherHandler(base::RunLoop* run_loop) : real_(run_loop) { - ON_CALL(*this, OnReceived(_, _, _)) - .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnReceived)); - ON_CALL(*this, OnDone(_)) - .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnDone)); - ON_CALL(*this, OnError(_, _)) - .WillByDefault(Invoke(&real_, &FetcherHandlerForTest::OnError)); - } - - MOCK_METHOD2(OnResponseStarted, - LoadResponseType( - Fetcher*, const scoped_refptr<net::HttpResponseHeaders>&)); - MOCK_METHOD3(OnReceived, void(Fetcher*, const char*, size_t)); - MOCK_METHOD1(OnDone, void(Fetcher*)); - MOCK_METHOD2(OnError, void(Fetcher*, const std::string&)); - - const std::string& data() const { return real_.data(); } - Fetcher* fetcher() const { return real_.fetcher(); } - - private: - FetcherHandlerForTest real_; -}; - -} // namespace class FileFetcherTest : public ::testing::Test { protected:
diff --git a/src/cobalt/loader/image/dummy_gif_image_decoder.h b/src/cobalt/loader/image/dummy_gif_image_decoder.h index 5c3775c..3ea5b38 100644 --- a/src/cobalt/loader/image/dummy_gif_image_decoder.h +++ b/src/cobalt/loader/image/dummy_gif_image_decoder.h
@@ -38,11 +38,6 @@ // From ImageDataDecoder std::string GetTypeString() const OVERRIDE { return "DummyGIFImageDecoder"; } - // Returns true if the signature is valid for the particular image type. - static bool IsValidSignature(const uint8* header) { - return !memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6); - } - private: // From ImageDataDecoder size_t DecodeChunkInternal(const uint8* data, size_t input_byte) OVERRIDE;
diff --git a/src/cobalt/loader/image/image_data_decoder.cc b/src/cobalt/loader/image/image_data_decoder.cc index 7d91bb8..3698bff 100644 --- a/src/cobalt/loader/image/image_data_decoder.cc +++ b/src/cobalt/loader/image/image_data_decoder.cc
@@ -102,11 +102,15 @@ return state_ == kDone; } -void ImageDataDecoder::AllocateImageData(const math::Size& size) { +void ImageDataDecoder::AllocateImageData(const math::Size& size, + bool has_alpha) { + DCHECK(resource_provider_->AlphaFormatSupported( + render_tree::kAlphaFormatOpaque)); DCHECK(resource_provider_->AlphaFormatSupported( render_tree::kAlphaFormatPremultiplied)); image_data_ = resource_provider_->AllocateImageData( - size, pixel_format(), render_tree::kAlphaFormatPremultiplied); + size, pixel_format(), has_alpha ? render_tree::kAlphaFormatPremultiplied + : render_tree::kAlphaFormatOpaque); } void ImageDataDecoder::CalculatePixelFormat() {
diff --git a/src/cobalt/loader/image/image_data_decoder.h b/src/cobalt/loader/image/image_data_decoder.h index 62beb19..ac19988 100644 --- a/src/cobalt/loader/image/image_data_decoder.h +++ b/src/cobalt/loader/image/image_data_decoder.h
@@ -22,6 +22,9 @@ #include "cobalt/render_tree/image.h" #include "cobalt/render_tree/resource_provider.h" +#if defined(STARBOARD) +#include "starboard/decode_target.h" +#endif namespace cobalt { namespace loader { @@ -44,6 +47,23 @@ return image_data_.Pass(); } +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + // Starboard version 3 adds support for hardware accelerated image decoding. + // In order to make use of this feature, subclasses of ImageDataDecoder may + // override this method in order to return an SbDecodeTarget, rather than a + // render_tree::ImageData, which could potentially save a copy from CPU + // memory to GPU memory, depending on the render_tree implementation and + // hardware image decoding functionality available. If + // |RetrieveSbDecodeTarget| returns any value other than the default + // |kSbDecodeInvalid|, ImageDecoder will interpret it as a preference + // towards SbDecodeTarget rather than render_tree::ImageData. + virtual SbDecodeTarget RetrieveSbDecodeTarget() { + return kSbDecodeTargetInvalid; + } +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) +#endif // defined(STARBOARD) + void DecodeChunk(const uint8* data, size_t size); // Return true if decoding succeeded. bool FinishWithSuccess(); @@ -63,7 +83,7 @@ // Subclass can override this function to get a last chance to do some work. virtual void FinishInternal() {} - void AllocateImageData(const math::Size& size); + void AllocateImageData(const math::Size& size, bool has_alpha); render_tree::ImageData* image_data() const { return image_data_.get(); }
diff --git a/src/cobalt/loader/image/image_decoder.cc b/src/cobalt/loader/image/image_decoder.cc index 5161dd3..5a55b9c 100644 --- a/src/cobalt/loader/image/image_decoder.cc +++ b/src/cobalt/loader/image/image_decoder.cc
@@ -20,6 +20,7 @@ #include "base/debug/trace_event.h" #include "cobalt/loader/image/dummy_gif_image_decoder.h" +#include "cobalt/loader/image/image_decoder_starboard.h" #if defined(__LB_PS3__) #include "cobalt/loader/image/jpeg_image_decoder_ps3.h" #else // defined(__LB_PS3__) @@ -30,6 +31,9 @@ #include "cobalt/loader/image/webp_image_decoder.h" #include "net/base/mime_util.h" #include "net/http/http_status_code.h" +#if defined(STARBOARD) +#include "starboard/image.h" +#endif namespace cobalt { namespace loader { @@ -49,6 +53,41 @@ result->append(message); } +// The various types of images we support decoding. +enum ImageType { + kImageTypeInvalid, + kImageTypeGIF, + kImageTypeJPEG, + kImageTypePNG, + kImageTypeWebP, +}; + +// Determine the ImageType of an image from its signature. +ImageType DetermineImageType(const uint8* header) { + if (!memcmp(header, "\xFF\xD8\xFF", 3)) { + return kImageTypeJPEG; + } else if (!memcmp(header, "GIF87a", 6) || !memcmp(header, "GIF89a", 6)) { + return kImageTypeGIF; + } else if (!memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8)) { + return kImageTypePNG; + } else if (!memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6)) { + return kImageTypeWebP; + } else { + return kImageTypeInvalid; + } +} + +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) +// clang-format off +SbDecodeTargetFormat kPreferredFormats[] = { + kSbDecodeTargetFormat1PlaneRGBA, + kSbDecodeTargetFormat1PlaneBGRA, +}; +// clang-format on +#endif +#endif + } // namespace ImageDecoder::ImageDecoder(render_tree::ResourceProvider* resource_provider, @@ -60,6 +99,7 @@ failure_callback_(failure_callback), error_callback_(error_callback), state_(kWaitingForHeader) { + TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::ImageDecoder()"); signature_cache_.position = 0; if (!resource_provider_) { @@ -70,6 +110,7 @@ LoadResponseType ImageDecoder::OnResponseStarted( Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers) { UNREFERENCED_PARAMETER(fetcher); + TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::OnResponseStarted()"); if (state_ == kSuspended) { DLOG(WARNING) << __FUNCTION__ << "[" << this << "] while suspended."; @@ -91,9 +132,8 @@ CacheMessage(&failure_message_, "No content returned."); } - std::string mime_style; - bool success = headers->GetMimeType(&mime_style); - if (!success || !net::IsSupportedImageMimeType(mime_style)) { + bool success = headers->GetMimeType(&mime_type_); + if (!success || !net::IsSupportedImageMimeType(mime_type_)) { state_ = kNotApplicable; CacheMessage(&failure_message_, "Not an image mime type."); } @@ -118,11 +158,22 @@ case kDecoding: DCHECK(decoder_); if (decoder_->FinishWithSuccess()) { - scoped_ptr<render_tree::ImageData> image_data = - decoder_->RetrieveImageData(); - success_callback_.Run( - image_data ? resource_provider_->CreateImage(image_data.Pass()) - : NULL); +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + SbDecodeTarget target = decoder_->RetrieveSbDecodeTarget(); + if (SbDecodeTargetIsValid(target)) { + success_callback_.Run( + resource_provider_->CreateImageFromSbDecodeTarget(target)); + } else // NOLINT +#endif +#endif + { + scoped_ptr<render_tree::ImageData> image_data = + decoder_->RetrieveImageData(); + success_callback_.Run( + image_data ? resource_provider_->CreateImage(image_data.Pass()) + : NULL); + } } else { error_callback_.Run(decoder_->GetTypeString() + " failed to decode image."); @@ -153,6 +204,7 @@ } bool ImageDecoder::Suspend() { + TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::Suspend()"); if (state_ == kDecoding) { DCHECK(decoder_); decoder_.reset(); @@ -164,6 +216,7 @@ } void ImageDecoder::Resume(render_tree::ResourceProvider* resource_provider) { + TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::Resume()"); if (state_ != kSuspended) { DCHECK_EQ(resource_provider_, resource_provider); return; @@ -178,6 +231,7 @@ } void ImageDecoder::DecodeChunkInternal(const uint8* input_bytes, size_t size) { + TRACE_EVENT0("cobalt::loader::image", "ImageDecoder::DecodeChunkInternal()"); switch (state_) { case kWaitingForHeader: { size_t consumed_size = 0; @@ -214,6 +268,8 @@ bool ImageDecoder::InitializeInternalDecoder(const uint8* input_bytes, size_t size, size_t* consumed_size) { + TRACE_EVENT0("cobalt::loader::image", + "ImageDecoder::InitializeInternalDecoder()"); const size_t index = signature_cache_.position; size_t fill_size = std::min(kLengthOfLongestSignature - index, size); memcpy(signature_cache_.data + index, input_bytes, fill_size); @@ -224,26 +280,50 @@ return false; } + const ImageType image_type = DetermineImageType(signature_cache_.data); + +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + const char* mime_type_c_string = mime_type_.c_str(); + + // Find out if any of our preferred formats are supported for this mime + // type. + SbDecodeTargetFormat format = kSbDecodeTargetFormatInvalid; + for (size_t i = 0; i < SB_ARRAY_SIZE(kPreferredFormats); ++i) { + if (SbImageIsDecodeSupported(mime_type_c_string, kPreferredFormats[i])) { + format = kPreferredFormats[i]; + break; + } + } + + if (SbDecodeTargetIsFormatValid(format) && + resource_provider_->SupportsSbDecodeTarget()) { + decoder_ = make_scoped_ptr<ImageDataDecoder>(new ImageDecoderStarboard( + resource_provider_, mime_type_c_string, format)); + return true; + } +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) +#endif // defined(STARBOARD) + // Call different types of decoders by matching the image signature. if (s_use_stub_image_decoder) { decoder_ = make_scoped_ptr<ImageDataDecoder>( new StubImageDecoder(resource_provider_)); + } else if (image_type == kImageTypeJPEG) { #if defined(__LB_PS3__) - } else if (JPEGImageDecoderPS3::IsValidSignature(signature_cache_.data)) { decoder_ = make_scoped_ptr<ImageDataDecoder>( new JPEGImageDecoderPS3(resource_provider_)); #else // defined(__LB_PS3__) - } else if (JPEGImageDecoder::IsValidSignature(signature_cache_.data)) { decoder_ = make_scoped_ptr<ImageDataDecoder>( new JPEGImageDecoder(resource_provider_)); #endif // defined(__LB_PS3__) - } else if (PNGImageDecoder::IsValidSignature(signature_cache_.data)) { + } else if (image_type == kImageTypePNG) { decoder_ = make_scoped_ptr<ImageDataDecoder>( new PNGImageDecoder(resource_provider_)); - } else if (WEBPImageDecoder::IsValidSignature(signature_cache_.data)) { + } else if (image_type == kImageTypeWebP) { decoder_ = make_scoped_ptr<ImageDataDecoder>( new WEBPImageDecoder(resource_provider_)); - } else if (DummyGIFImageDecoder::IsValidSignature(signature_cache_.data)) { + } else if (image_type == kImageTypeGIF) { decoder_ = make_scoped_ptr<ImageDataDecoder>( new DummyGIFImageDecoder(resource_provider_)); } else {
diff --git a/src/cobalt/loader/image/image_decoder.h b/src/cobalt/loader/image/image_decoder.h index 610d27b..00902bc 100644 --- a/src/cobalt/loader/image/image_decoder.h +++ b/src/cobalt/loader/image/image_decoder.h
@@ -89,6 +89,7 @@ SignatureCache signature_cache_; State state_; std::string failure_message_; + std::string mime_type_; }; } // namespace image
diff --git a/src/cobalt/loader/image/image_decoder_starboard.cc b/src/cobalt/loader/image/image_decoder_starboard.cc new file mode 100644 index 0000000..1fdb7bf --- /dev/null +++ b/src/cobalt/loader/image/image_decoder_starboard.cc
@@ -0,0 +1,78 @@ +/* + * Copyright 2016 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. + */ + +#if defined(STARBOARD) + +#include "cobalt/loader/image/image_decoder_starboard.h" + +#include <algorithm> + +#include "base/debug/trace_event.h" +#include "base/logging.h" +#include "starboard/decode_target.h" +#include "starboard/image.h" + +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + +namespace cobalt { +namespace loader { +namespace image { + +ImageDecoderStarboard::ImageDecoderStarboard( + render_tree::ResourceProvider* resource_provider, const char* mime_type, + SbDecodeTargetFormat format) + : ImageDataDecoder(resource_provider), + mime_type_(mime_type), + format_(format), + provider_(resource_provider->GetSbDecodeTargetProvider()), + target_(kSbDecodeTargetInvalid) { + TRACE_EVENT0("cobalt::loader::image", + "ImageDecoderStarboard::ImageDecoderStarboard()"); +} + +ImageDecoderStarboard::~ImageDecoderStarboard() {} + +size_t ImageDecoderStarboard::DecodeChunkInternal(const uint8* data, + size_t input_byte) { + TRACE_EVENT0("cobalt::loader::image", + "ImageDecoderStarboard::DecodeChunkInternal()"); + + buffer_.insert(buffer_.end(), data, data + input_byte); + return input_byte; +} + +void ImageDecoderStarboard::FinishInternal() { + TRACE_EVENT0("cobalt::loader::image", + "ImageDecoderStarboard::FinishInternal()"); + DCHECK(!buffer_.empty()); + DCHECK(SbImageIsDecodeSupported(mime_type_, format_)); + target_ = + SbImageDecode(provider_, &buffer_[0], static_cast<int>(buffer_.size()), + mime_type_, format_); + if (SbDecodeTargetIsValid(target_)) { + set_state(kDone); + } else { + set_state(kError); + } +} + +} // namespace image +} // namespace loader +} // namespace cobalt + +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) + +#endif // #if defined(STARBOARD)
diff --git a/src/cobalt/loader/image/image_decoder_starboard.h b/src/cobalt/loader/image/image_decoder_starboard.h new file mode 100644 index 0000000..ac14d4c --- /dev/null +++ b/src/cobalt/loader/image/image_decoder_starboard.h
@@ -0,0 +1,68 @@ +/* + * Copyright 2016 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_LOADER_IMAGE_IMAGE_DECODER_STARBOARD_H_ +#define COBALT_LOADER_IMAGE_IMAGE_DECODER_STARBOARD_H_ + +#if defined(STARBOARD) + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "cobalt/loader/image/image_data_decoder.h" +#include "starboard/decode_target.h" + +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + +namespace cobalt { +namespace loader { +namespace image { + +class ImageDecoderStarboard : public ImageDataDecoder { + public: + explicit ImageDecoderStarboard( + render_tree::ResourceProvider* resource_provider, const char* mime_type, + SbDecodeTargetFormat format); + ~ImageDecoderStarboard() OVERRIDE; + + // From ImageDataDecoder + std::string GetTypeString() const OVERRIDE { return "ImageDecoderStarboard"; } + + SbDecodeTarget RetrieveSbDecodeTarget() OVERRIDE { return target_; } + + private: + // From ImageDataDecoder + size_t DecodeChunkInternal(const uint8* data, size_t size) OVERRIDE; + void FinishInternal() OVERRIDE; + + const char* mime_type_; + SbDecodeTargetFormat format_; + std::vector<uint8> buffer_; + SbDecodeTargetProvider* provider_; + SbDecodeTarget target_; +}; + +} // namespace image +} // namespace loader +} // namespace cobalt + +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) + +#endif // defined(STARBOARD) + +#endif // COBALT_LOADER_IMAGE_IMAGE_DECODER_STARBOARD_H_
diff --git a/src/cobalt/loader/image/jpeg_image_decoder.cc b/src/cobalt/loader/image/jpeg_image_decoder.cc index a249c7e..29b99dc 100644 --- a/src/cobalt/loader/image/jpeg_image_decoder.cc +++ b/src/cobalt/loader/image/jpeg_image_decoder.cc
@@ -84,6 +84,7 @@ JPEGImageDecoder::JPEGImageDecoder( render_tree::ResourceProvider* resource_provider) : ImageDataDecoder(resource_provider) { + TRACE_EVENT0("cobalt::loader::image", "JPEGImageDecoder::JPEGImageDecoder()"); memset(&info_, 0, sizeof(info_)); memset(&source_manager_, 0, sizeof(source_manager_)); @@ -111,6 +112,8 @@ size_t JPEGImageDecoder::DecodeChunkInternal(const uint8* data, size_t input_byte) { + TRACE_EVENT0("cobalt::loader::image", + "JPEGImageDecoder::DecodeChunkInternal()"); // |client_data| is available for use by application. jmp_buf jump_buffer; info_.client_data = &jump_buffer; @@ -175,6 +178,7 @@ } bool JPEGImageDecoder::ReadHeader() { + TRACE_EVENT0("cobalt::loader::image", "JPEGImageDecoder::ReadHeader()"); if (jpeg_read_header(&info_, true) == JPEG_SUSPENDED) { // Since |jpeg_read_header| doesn't have enough data, go back to the state // before reading the header. @@ -183,11 +187,13 @@ } AllocateImageData(math::Size(static_cast<int>(info_.image_width), - static_cast<int>(info_.image_height))); + static_cast<int>(info_.image_height)), + false); return true; } bool JPEGImageDecoder::StartDecompress() { + TRACE_EVENT0("cobalt::loader::image", "JPEGImageDecoder::StartDecompress()"); // jpeg_has_multiple_scans() returns TRUE if the incoming image file has more // than one scan. info_.buffered_image = jpeg_has_multiple_scans(&info_); @@ -214,6 +220,8 @@ // TODO: support displaying the low resolution image while decoding // the progressive JPEG. bool JPEGImageDecoder::DecodeProgressiveJPEG() { + TRACE_EVENT0("cobalt::loader::image", + "JPEGImageDecoder::DecodeProgressiveJPEG()"); int status; do { // |jpeg_consume_input| decodes the input data as it arrives. @@ -290,6 +298,8 @@ } bool JPEGImageDecoder::ReadLines() { + TRACE_EVENT0("cobalt::loader::image", "JPEGImageDecoder::ReadLines()"); + // Creation of 2-D sample arrays which is for one row. // See the comments in jmemmgr.c. JSAMPARRAY buffer = (*info_.mem->alloc_sarray)(
diff --git a/src/cobalt/loader/image/jpeg_image_decoder.h b/src/cobalt/loader/image/jpeg_image_decoder.h index 9d8c487..242fd0e 100644 --- a/src/cobalt/loader/image/jpeg_image_decoder.h +++ b/src/cobalt/loader/image/jpeg_image_decoder.h
@@ -41,11 +41,6 @@ // From ImageDataDecoder std::string GetTypeString() const OVERRIDE { return "JPEGImageDecoder"; } - // Returns true if the signature is valid for the particular image type. - static bool IsValidSignature(const uint8* header) { - return !memcmp(header, "\xFF\xD8\xFF", 3); - } - private: // From ImageDataDecoder size_t DecodeChunkInternal(const uint8* data, size_t size) OVERRIDE;
diff --git a/src/cobalt/loader/image/png_image_decoder.cc b/src/cobalt/loader/image/png_image_decoder.cc index e048d41..b09e637 100644 --- a/src/cobalt/loader/image/png_image_decoder.cc +++ b/src/cobalt/loader/image/png_image_decoder.cc
@@ -16,6 +16,7 @@ #include "cobalt/loader/image/png_image_decoder.h" +#include "base/debug/trace_event.h" #include "base/logging.h" namespace cobalt { @@ -72,6 +73,8 @@ info_(NULL), has_alpha_(false), interlace_buffer_(0) { + TRACE_EVENT0("cobalt::loader::image", "PNGImageDecoder::PNGImageDecoder()"); + png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, DecodingFailed, DecodingWarning); info_ = png_create_info_struct(png_); @@ -80,6 +83,8 @@ } size_t PNGImageDecoder::DecodeChunkInternal(const uint8* data, size_t size) { + TRACE_EVENT0("cobalt::loader::image", + "PNGImageDecoder::DecodeChunkInternal()"); // int setjmp(jmp_buf env) saves the current environment (ths program state), // at some point of program execution, into a platform-specific data // structure (jmp_buf) that can be used at some later point of program @@ -107,6 +112,7 @@ } PNGImageDecoder::~PNGImageDecoder() { + TRACE_EVENT0("cobalt::loader::image", "PNGImageDecoder::~PNGImageDecoder()"); // Both are created at the same time. So they should be both zero // or both non-zero. Use && here to be safer. if (png_ && info_) { @@ -127,6 +133,7 @@ // static void PNGImageDecoder::HeaderAvailable(png_structp png, png_infop info) { UNREFERENCED_PARAMETER(info); + TRACE_EVENT0("cobalt::loader::image", "PNGImageDecoder::~PNGImageDecoder()"); PNGImageDecoder* decoder = static_cast<PNGImageDecoder*>(png_get_progressive_ptr(png)); decoder->HeaderAvailableCallback(); @@ -146,12 +153,16 @@ // static void PNGImageDecoder::DecodeDone(png_structp png, png_infop info) { UNREFERENCED_PARAMETER(info); + TRACE_EVENT0("cobalt::loader::image", "PNGImageDecoder::DecodeDone()"); + PNGImageDecoder* decoder = static_cast<PNGImageDecoder*>(png_get_progressive_ptr(png)); decoder->DecodeDoneCallback(); } void PNGImageDecoder::HeaderAvailableCallback() { + TRACE_EVENT0("cobalt::loader::image", + "PNGImageDecoder::HeaderAvailableCallback()"); DCHECK_EQ(state(), kWaitingForHeader); png_uint_32 width = png_get_image_width(png_, info_); @@ -236,7 +247,8 @@ } AllocateImageData( - math::Size(static_cast<int>(width), static_cast<int>(height))); + math::Size(static_cast<int>(width), static_cast<int>(height)), + has_alpha_); set_state(kReadLines); }
diff --git a/src/cobalt/loader/image/png_image_decoder.h b/src/cobalt/loader/image/png_image_decoder.h index 29a3887..d270087 100644 --- a/src/cobalt/loader/image/png_image_decoder.h +++ b/src/cobalt/loader/image/png_image_decoder.h
@@ -36,11 +36,6 @@ // From ImageDataDecoder std::string GetTypeString() const OVERRIDE { return "PNGImageDecoder"; } - // Returns true if the signature is valid for the particular image type. - static bool IsValidSignature(const uint8* header) { - return !memcmp(header, "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", 8); - } - private: // From ImageDataDecoder size_t DecodeChunkInternal(const uint8* data, size_t input_byte) OVERRIDE;
diff --git a/src/cobalt/loader/image/stub_image_decoder.h b/src/cobalt/loader/image/stub_image_decoder.h index 99c3979..1d8de85 100644 --- a/src/cobalt/loader/image/stub_image_decoder.h +++ b/src/cobalt/loader/image/stub_image_decoder.h
@@ -47,7 +47,7 @@ UNREFERENCED_PARAMETER(data); UNREFERENCED_PARAMETER(input_byte); if (!image_data()) { - AllocateImageData(math::Size(4, 4)); + AllocateImageData(math::Size(4, 4), true); } set_state(kDone); return input_byte;
diff --git a/src/cobalt/loader/image/threaded_image_decoder_proxy.cc b/src/cobalt/loader/image/threaded_image_decoder_proxy.cc new file mode 100644 index 0000000..53fa8ce --- /dev/null +++ b/src/cobalt/loader/image/threaded_image_decoder_proxy.cc
@@ -0,0 +1,134 @@ +/* + * Copyright 2016 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/loader/image/threaded_image_decoder_proxy.h" + +#include <string> + +#include "base/bind.h" +#include "base/threading/thread.h" +#include "cobalt/loader/image/image_decoder.h" + +namespace cobalt { +namespace loader { +namespace image { + +namespace { + +// Helper function that is run on the WebModule thread to run a Callback if +// and only if |threaded_image_decoder_proxy| is still alive. +template <typename Callback, typename Arg> +void MaybeRun( + base::WeakPtr<ThreadedImageDecoderProxy> threaded_image_decoder_proxy, + const Callback& callback, const Arg& arg) { + if (threaded_image_decoder_proxy) { + callback.Run(arg); + } +} + +// Helper function to post a Callback back to the WebModule thread, that +// checks whether the ThreadedImageDecoderProxy it came from is alive or not +// before it runs. +template <typename Callback, typename Arg> +void PostToMessageLoopChecked( + base::WeakPtr<ThreadedImageDecoderProxy> threaded_image_decoder_proxy, + const Callback& callback, MessageLoop* message_loop, const Arg& arg) { + message_loop->PostTask( + FROM_HERE, base::Bind(&MaybeRun<Callback, Arg>, + threaded_image_decoder_proxy, callback, arg)); +} + +} // namespace + +ThreadedImageDecoderProxy::ThreadedImageDecoderProxy( + render_tree::ResourceProvider* resource_provider, + const SuccessCallback& success_callback, + const FailureCallback& failure_callback, + const ErrorCallback& error_callback, MessageLoop* load_message_loop) + : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST( + weak_this_(weak_ptr_factory_.GetWeakPtr())), + load_message_loop_(load_message_loop), + result_message_loop_(MessageLoop::current()), + image_decoder_(new ImageDecoder( + resource_provider, + base::Bind( + &PostToMessageLoopChecked<SuccessCallback, + scoped_refptr<render_tree::Image> >, + weak_this_, success_callback, result_message_loop_), + base::Bind(&PostToMessageLoopChecked<FailureCallback, std::string>, + weak_this_, failure_callback, result_message_loop_), + base::Bind(&PostToMessageLoopChecked<ErrorCallback, std::string>, + weak_this_, error_callback, result_message_loop_))) { + DCHECK(load_message_loop_); + DCHECK(result_message_loop_); + DCHECK(image_decoder_); +} + +// We're dying, but |image_decoder_| might still be doing work on the load +// thread. Because of this, we transfer ownership of it into the load message +// loop, where it will be deleted after any pending tasks involving it are +// done. +ThreadedImageDecoderProxy::~ThreadedImageDecoderProxy() { + load_message_loop_->DeleteSoon(FROM_HERE, image_decoder_.release()); +} + +LoadResponseType ThreadedImageDecoderProxy::OnResponseStarted( + Fetcher* fetcher, const scoped_refptr<net::HttpResponseHeaders>& headers) { + UNREFERENCED_PARAMETER(fetcher); + return image_decoder_->OnResponseStarted(fetcher, headers); +} + +void ThreadedImageDecoderProxy::DecodeChunk(const char* data, size_t size) { + scoped_ptr<std::string> scoped_data(new std::string(data, size)); + load_message_loop_->PostTask( + FROM_HERE, base::Bind(&Decoder::DecodeChunkPassed, + base::Unretained(image_decoder_.get()), + base::Passed(&scoped_data))); +} + +void ThreadedImageDecoderProxy::DecodeChunkPassed( + scoped_ptr<std::string> data) { + load_message_loop_->PostTask( + FROM_HERE, + base::Bind(&Decoder::DecodeChunkPassed, + base::Unretained(image_decoder_.get()), base::Passed(&data))); +} + +void ThreadedImageDecoderProxy::Finish() { + load_message_loop_->PostTask( + FROM_HERE, base::Bind(&ImageDecoder::Finish, + base::Unretained(image_decoder_.get()))); +} + +bool ThreadedImageDecoderProxy::Suspend() { + load_message_loop_->PostTask( + FROM_HERE, base::Bind(base::IgnoreResult(&ImageDecoder::Suspend), + base::Unretained(image_decoder_.get()))); + return true; +} + +void ThreadedImageDecoderProxy::Resume( + render_tree::ResourceProvider* resource_provider) { + load_message_loop_->PostTask( + FROM_HERE, + base::Bind(&ImageDecoder::Resume, base::Unretained(image_decoder_.get()), + resource_provider)); +} + +} // namespace image +} // namespace loader +} // namespace cobalt
diff --git a/src/cobalt/loader/image/threaded_image_decoder_proxy.h b/src/cobalt/loader/image/threaded_image_decoder_proxy.h new file mode 100644 index 0000000..c6b559b --- /dev/null +++ b/src/cobalt/loader/image/threaded_image_decoder_proxy.h
@@ -0,0 +1,83 @@ +/* + * Copyright 2016 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_LOADER_IMAGE_THREADED_IMAGE_DECODER_PROXY_H_ +#define COBALT_LOADER_IMAGE_THREADED_IMAGE_DECODER_PROXY_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "cobalt/loader/decoder.h" +#include "cobalt/loader/image/image_data_decoder.h" +#include "cobalt/loader/image/image_decoder.h" +#include "cobalt/render_tree/image.h" +#include "cobalt/render_tree/resource_provider.h" + +namespace cobalt { +namespace loader { +namespace image { + +// This class handles the layer of indirection between images that web module +// thread wants to decode, and the ResourceLoader thread that does the actual +// work. +class ThreadedImageDecoderProxy : public Decoder { + public: + typedef base::Callback<void(const scoped_refptr<render_tree::Image>&)> + SuccessCallback; + typedef base::Callback<void(const std::string&)> FailureCallback; + typedef base::Callback<void(const std::string&)> ErrorCallback; + + ThreadedImageDecoderProxy(render_tree::ResourceProvider* resource_provider, + const SuccessCallback& success_callback, + const FailureCallback& failure_callback, + const ErrorCallback& error_callback, + MessageLoop* load_message_loop_); + + ~ThreadedImageDecoderProxy(); + + // From the Decoder interface. + LoadResponseType OnResponseStarted( + Fetcher* fetcher, + const scoped_refptr<net::HttpResponseHeaders>& headers) OVERRIDE; + void DecodeChunk(const char* data, size_t size) OVERRIDE; + void DecodeChunkPassed(scoped_ptr<std::string> data) OVERRIDE; + void Finish() OVERRIDE; + bool Suspend() OVERRIDE; + void Resume(render_tree::ResourceProvider* resource_provider) OVERRIDE; + + private: + base::WeakPtrFactory<ThreadedImageDecoderProxy> weak_ptr_factory_; + base::WeakPtr<ThreadedImageDecoderProxy> weak_this_; + + // The message loop that |image_decoder_| should run on. + MessageLoop* load_message_loop_; + + // The message loop that the original callbacks passed into us should run + // on. + MessageLoop* result_message_loop_; + + // The actual image decoder. + scoped_ptr<ImageDecoder> image_decoder_; +}; + +} // namespace image +} // namespace loader +} // namespace cobalt + +#endif // COBALT_LOADER_IMAGE_THREADED_IMAGE_DECODER_PROXY_H_
diff --git a/src/cobalt/loader/image/webp_image_decoder.cc b/src/cobalt/loader/image/webp_image_decoder.cc index b735d54..cff8594 100644 --- a/src/cobalt/loader/image/webp_image_decoder.cc +++ b/src/cobalt/loader/image/webp_image_decoder.cc
@@ -16,6 +16,7 @@ #include "cobalt/loader/image/webp_image_decoder.h" +#include "base/debug/trace_event.h" #include "base/logging.h" namespace cobalt { @@ -25,6 +26,7 @@ WEBPImageDecoder::WEBPImageDecoder( render_tree::ResourceProvider* resource_provider) : ImageDataDecoder(resource_provider), internal_decoder_(NULL) { + TRACE_EVENT0("cobalt::loader::image", "WEBPImageDecoder::WEBPImageDecoder()"); // Initialize the configuration as empty. WebPInitDecoderConfig(&config_); // Skip the in-loop filtering. @@ -37,10 +39,16 @@ config_.options.no_enhancement = 1; } -WEBPImageDecoder::~WEBPImageDecoder() { DeleteInternalDecoder(); } +WEBPImageDecoder::~WEBPImageDecoder() { + TRACE_EVENT0("cobalt::loader::image", + "WEBPImageDecoder::~WEBPImageDecoder()"); + DeleteInternalDecoder(); +} size_t WEBPImageDecoder::DecodeChunkInternal(const uint8* data, size_t input_byte) { + TRACE_EVENT0("cobalt::loader::image", + "WEBPImageDecoder::DecodeChunkInternal()"); const uint8* next_input_byte = data; size_t bytes_in_buffer = input_byte; @@ -81,6 +89,7 @@ bool WEBPImageDecoder::ReadHeader(const uint8* data, size_t size, bool* has_alpha) { + TRACE_EVENT0("cobalt::loader::image", "WEBPImageDecoder::ReadHeader()"); // Retrieve features from the bitstream. The *features structure is filled // with information gathered from the bitstream. // Returns VP8_STATUS_OK when the features are successfully retrieved. Returns @@ -92,7 +101,7 @@ int height = config_.input.height; *has_alpha = !!config_.input.has_alpha; - AllocateImageData(math::Size(width, height)); + AllocateImageData(math::Size(width, height), *has_alpha); return true; } else if (status == VP8_STATUS_NOT_ENOUGH_DATA) { // Data is not enough for decoding the header. @@ -105,6 +114,8 @@ } bool WEBPImageDecoder::CreateInternalDecoder(bool has_alpha) { + TRACE_EVENT0("cobalt::loader::image", + "WEBPImageDecoder::CreateInternalDecoder()"); config_.output.colorspace = has_alpha ? MODE_rgbA : MODE_RGBA; config_.output.u.RGBA.stride = image_data()->GetDescriptor().pitch_in_bytes; config_.output.u.RGBA.size = @@ -127,6 +138,8 @@ } void WEBPImageDecoder::DeleteInternalDecoder() { + TRACE_EVENT0("cobalt::loader::image", + "WEBPImageDecoder::DeleteInternalDecoder()"); if (internal_decoder_) { // Deletes the WebPIDecoder object and associated memory. Must always be // called if WebPIDecode succeeded.
diff --git a/src/cobalt/loader/image/webp_image_decoder.h b/src/cobalt/loader/image/webp_image_decoder.h index da9ce59..187ca62 100644 --- a/src/cobalt/loader/image/webp_image_decoder.h +++ b/src/cobalt/loader/image/webp_image_decoder.h
@@ -34,11 +34,6 @@ // From ImageDataDecoder std::string GetTypeString() const OVERRIDE { return "WEBPImageDecoder"; } - // Returns true if the signature is valid for the particular image type. - static bool IsValidSignature(const uint8* header) { - return !memcmp(header, "RIFF", 4) && !memcmp(header + 8, "WEBPVP", 6); - } - private: // From ImageDataDecoder size_t DecodeChunkInternal(const uint8* data, size_t input_byte) OVERRIDE;
diff --git a/src/cobalt/loader/loader.cc b/src/cobalt/loader/loader.cc index a5d86e7..b01a21a 100644 --- a/src/cobalt/loader/loader.cc +++ b/src/cobalt/loader/loader.cc
@@ -51,6 +51,11 @@ UNREFERENCED_PARAMETER(fetcher); decoder_->DecodeChunk(data, size); } + void OnReceivedPassed(Fetcher* fetcher, + scoped_ptr<std::string> data) OVERRIDE { + UNREFERENCED_PARAMETER(fetcher); + decoder_->DecodeChunkPassed(data.Pass()); + } void OnDone(Fetcher* fetcher) OVERRIDE { UNREFERENCED_PARAMETER(fetcher); decoder_->Finish();
diff --git a/src/cobalt/loader/loader.gyp b/src/cobalt/loader/loader.gyp index c0a7f79..f3460d8 100644 --- a/src/cobalt/loader/loader.gyp +++ b/src/cobalt/loader/loader.gyp
@@ -21,6 +21,8 @@ 'target_name': 'loader', 'type': 'static_library', 'sources': [ + 'blob_fetcher.cc', + 'blob_fetcher.h', 'decoder.h', 'embedded_fetcher.cc', 'embedded_fetcher.h', @@ -30,9 +32,9 @@ 'fetcher_factory.h', 'file_fetcher.cc', 'file_fetcher.h', + 'font/remote_typeface_cache.h', 'font/typeface_decoder.cc', 'font/typeface_decoder.h', - 'font/remote_typeface_cache.h', 'image/dummy_gif_image_decoder.cc', 'image/dummy_gif_image_decoder.h', 'image/image_cache.h', @@ -40,11 +42,15 @@ 'image/image_data_decoder.h', 'image/image_decoder.cc', 'image/image_decoder.h', + 'image/image_decoder_starboard.cc', + 'image/image_decoder_starboard.h', 'image/jpeg_image_decoder.cc', 'image/jpeg_image_decoder.h', 'image/png_image_decoder.cc', 'image/png_image_decoder.h', 'image/stub_image_decoder.h', + 'image/threaded_image_decoder_proxy.cc', + 'image/threaded_image_decoder_proxy.h', 'image/webp_image_decoder.cc', 'image/webp_image_decoder.h', 'loader.cc', @@ -91,7 +97,9 @@ 'target_name': 'loader_test', 'type': '<(gtest_target_type)', 'sources': [ + 'blob_fetcher_test.cc', 'fetcher_factory_test.cc', + 'fetcher_test.h', 'file_fetcher_test.cc', 'font/typeface_decoder_test.cc', 'image/image_decoder_test.cc',
diff --git a/src/cobalt/loader/loader_factory.cc b/src/cobalt/loader/loader_factory.cc index e39a512..c3d2138 100644 --- a/src/cobalt/loader/loader_factory.cc +++ b/src/cobalt/loader/loader_factory.cc
@@ -16,13 +16,28 @@ #include "cobalt/loader/loader_factory.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "cobalt/loader/image/threaded_image_decoder_proxy.h" + namespace cobalt { namespace loader { +namespace { +// The ResourceLoader thread uses the default stack size, which is requested +// by passing in 0 for its stack size. +const size_t kLoadThreadStackSize = 0; +} // namespace + LoaderFactory::LoaderFactory(FetcherFactory* fetcher_factory, render_tree::ResourceProvider* resource_provider) : fetcher_factory_(fetcher_factory), - resource_provider_(resource_provider) {} + resource_provider_(resource_provider), + load_thread_("ResourceLoader") { + base::Thread::Options options(MessageLoop::TYPE_DEFAULT, kLoadThreadStackSize, + base::kThreadPriority_Low); + load_thread_.StartWithOptions(options); +} scoped_ptr<Loader> LoaderFactory::CreateImageLoader( const GURL& url, const csp::SecurityCallback& url_security_callback, @@ -33,9 +48,9 @@ scoped_ptr<Loader> loader(new Loader( MakeFetcherCreator(url, url_security_callback), - scoped_ptr<Decoder>( - new image::ImageDecoder(resource_provider_, success_callback, - failure_callback, error_callback)), + scoped_ptr<Decoder>(new image::ThreadedImageDecoderProxy( + resource_provider_, success_callback, failure_callback, + error_callback, load_thread_.message_loop())), error_callback, base::Bind(&LoaderFactory::OnLoaderDestroyed, base::Unretained(this)))); OnLoaderCreated(loader.get()); @@ -78,6 +93,13 @@ iter != active_loaders_.end(); ++iter) { (*iter)->Suspend(); } + + // Wait for all loader thread messages to be flushed before returning. + base::WaitableEvent messages_flushed(true, false); + load_thread_.message_loop()->PostTask( + FROM_HERE, base::Bind(&base::WaitableEvent::Signal, + base::Unretained(&messages_flushed))); + messages_flushed.Wait(); } void LoaderFactory::Resume(render_tree::ResourceProvider* resource_provider) {
diff --git a/src/cobalt/loader/loader_factory.h b/src/cobalt/loader/loader_factory.h index 000e7ad..fe79673 100644 --- a/src/cobalt/loader/loader_factory.h +++ b/src/cobalt/loader/loader_factory.h
@@ -19,6 +19,7 @@ #include <set> +#include "base/threading/thread.h" #include "cobalt/csp/content_security_policy.h" #include "cobalt/loader/fetcher_factory.h" #include "cobalt/loader/font/typeface_decoder.h" @@ -81,6 +82,10 @@ // can be aborted. typedef std::set<Loader*> LoaderSet; LoaderSet active_loaders_; + + // Thread to run asynchronous fetchers and decoders on. At the moment, + // image decoding is the only thing done on this thread. + base::Thread load_thread_; }; } // namespace loader
diff --git a/src/cobalt/loader/loader_test.cc b/src/cobalt/loader/loader_test.cc index c7e637c..ed43dfa 100644 --- a/src/cobalt/loader/loader_test.cc +++ b/src/cobalt/loader/loader_test.cc
@@ -100,7 +100,7 @@ struct MockFetcherFactory { public: - MockFetcherFactory(): count(0) {} + MockFetcherFactory() : count(0) {} // Way to access the last fetcher created by the fetcher factory. MockFetcher* fetcher; int count;
diff --git a/src/cobalt/loader/mock_loader_factory.h b/src/cobalt/loader/mock_loader_factory.h new file mode 100644 index 0000000..b5314d6 --- /dev/null +++ b/src/cobalt/loader/mock_loader_factory.h
@@ -0,0 +1,82 @@ +/* + * Copyright 2016 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_LOADER_MOCK_LOADER_FACTORY_H_ +#define COBALT_LOADER_MOCK_LOADER_FACTORY_H_ + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/memory/scoped_ptr.h" +#include "cobalt/csp/content_security_policy.h" +#include "cobalt/loader/font/typeface_decoder.h" +#include "cobalt/loader/image/image_decoder.h" +#include "cobalt/loader/loader.h" +#include "cobalt/loader/loader_factory.h" +#include "googleurl/src/gurl.h" + +namespace cobalt { +namespace loader { + +class MockLoaderFactory { + public: + MOCK_METHOD5(CreateImageLoaderMock, + loader::Loader*( + const GURL& url, + const csp::SecurityCallback& url_security_callback, + const image::ImageDecoder::SuccessCallback& success_callback, + const image::ImageDecoder::FailureCallback& failure_callback, + const image::ImageDecoder::ErrorCallback& error_callback)); + + MOCK_METHOD5( + CreateTypefaceLoaderMock, + loader::Loader*( + const GURL& url, const csp::SecurityCallback& url_security_callback, + const font::TypefaceDecoder::SuccessCallback& success_callback, + const font::TypefaceDecoder::FailureCallback& failure_callback, + const font::TypefaceDecoder::ErrorCallback& error_callback)); + + // This workaround is required since scoped_ptr has no copy constructor. + // see: + // https://groups.google.com/a/chromium.org/forum/#!msg/chromium-dev/01sDxsJ1OYw/I_S0xCBRF2oJ + scoped_ptr<Loader> CreateImageLoader( + const GURL& url, const csp::SecurityCallback& url_security_callback, + const image::ImageDecoder::SuccessCallback& success_callback, + const image::ImageDecoder::FailureCallback& failure_callback, + const image::ImageDecoder::ErrorCallback& error_callback) { + return scoped_ptr<Loader>( + CreateImageLoaderMock(url, url_security_callback, success_callback, + failure_callback, error_callback)); + } + + scoped_ptr<Loader> CreateTypefaceLoader( + const GURL& url, const csp::SecurityCallback& url_security_callback, + const font::TypefaceDecoder::SuccessCallback& success_callback, + const font::TypefaceDecoder::FailureCallback& failure_callback, + const font::TypefaceDecoder::ErrorCallback& error_callback) { + return scoped_ptr<Loader>( + CreateTypefaceLoaderMock(url, url_security_callback, success_callback, + failure_callback, error_callback)); + } + + MOCK_METHOD0(Suspend, void()); + MOCK_METHOD1(Resume, void(render_tree::ResourceProvider* resource_provider)); +}; + +} // namespace loader +} // namespace cobalt + +#endif // COBALT_LOADER_MOCK_LOADER_FACTORY_H_
diff --git a/src/cobalt/loader/net_fetcher.cc b/src/cobalt/loader/net_fetcher.cc index 4ba9d17..efb946e 100644 --- a/src/cobalt/loader/net_fetcher.cc +++ b/src/cobalt/loader/net_fetcher.cc
@@ -21,12 +21,46 @@ #include "base/stringprintf.h" #include "cobalt/network/network_module.h" #include "net/url_request/url_fetcher.h" +#if defined(OS_STARBOARD) +#include "starboard/configuration.h" +#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +#define HANDLE_CORE_DUMP +#include "base/lazy_instance.h" +#include "starboard/ps4/core_dump_handler.h" +#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +#endif // OS_STARBOARD namespace cobalt { namespace loader { namespace { +#if defined(HANDLE_CORE_DUMP) + +class NetFetcherLog { + public: + NetFetcherLog() : total_fetched_bytes_(0) { + SbCoreDumpRegisterHandler(CoreDumpHandler, this); + } + ~NetFetcherLog() { SbCoreDumpUnregisterHandler(CoreDumpHandler, this); } + + static void CoreDumpHandler(void* context) { + SbCoreDumpLogInteger( + "NetFetcher total fetched bytes", + static_cast<NetFetcherLog*>(context)->total_fetched_bytes_); + } + + void IncrementFetchedBytes(int length) { total_fetched_bytes_ += length; } + + private: + int total_fetched_bytes_; + DISALLOW_COPY_AND_ASSIGN(NetFetcherLog); +}; + +base::LazyInstance<NetFetcherLog> net_fetcher_log = LAZY_INSTANCE_INITIALIZER; + +#endif // defined(HANDLE_CORE_DUMP) + bool IsResponseCodeSuccess(int response_code) { // NetFetcher only considers success to be if the network request // was successful *and* we get a 2xx response back. @@ -47,32 +81,33 @@ const Options& options) : Fetcher(handler), security_callback_(security_callback), - ALLOW_THIS_IN_INITIALIZER_LIST(start_callback_( - base::Bind(&NetFetcher::Start, base::Unretained(this)))) { + ALLOW_THIS_IN_INITIALIZER_LIST(csp_reject_callback_( + base::Bind(&NetFetcher::ProcessCSPReject, base::Unretained(this)))) { url_fetcher_.reset( net::URLFetcher::Create(url, options.request_method, this)); url_fetcher_->SetRequestContext(network_module->url_request_context_getter()); url_fetcher_->DiscardResponse(); - // Delay the actual start until this function is complete. Otherwise we might - // call handler's callbacks at an unexpected time- e.g. receiving OnError() - // while a loader is still being constructed. - MessageLoop::current()->PostTask(FROM_HERE, start_callback_.callback()); -} - -void NetFetcher::Start() { - DCHECK(thread_checker_.CalledOnValidThread()); const GURL& original_url = url_fetcher_->GetOriginalURL(); if (security_callback_.is_null() || security_callback_.Run(original_url, false /* did not redirect */)) { url_fetcher_->Start(); } else { - std::string msg(base::StringPrintf("URL %s rejected by security policy.", - original_url.spec().c_str())); - return HandleError(msg).InvalidateThis(); + // Delay the callback until this function is complete. Otherwise we might + // call handler's callbacks at an unexpected time- e.g. receiving OnError() + // while a loader is still being constructed. + MessageLoop::current()->PostTask(FROM_HERE, + csp_reject_callback_.callback()); } } +void NetFetcher::ProcessCSPReject() { + const GURL& original_url = url_fetcher_->GetOriginalURL(); + std::string msg(base::StringPrintf("URL %s rejected by security policy.", + original_url.spec().c_str())); + return HandleError(msg).InvalidateThis(); +} + void NetFetcher::OnURLFetchResponseStarted(const net::URLFetcher* source) { DCHECK(thread_checker_.CalledOnValidThread()); if (source->GetURL() != source->GetOriginalURL()) { @@ -118,13 +153,17 @@ void NetFetcher::OnURLFetchDownloadData(const net::URLFetcher* source, scoped_ptr<std::string> download_data) { if (IsResponseCodeSuccess(source->GetResponseCode())) { - handler()->OnReceived(this, download_data->data(), download_data->length()); +#if defined(HANDLE_CORE_DUMP) + net_fetcher_log.Get().IncrementFetchedBytes( + static_cast<int>(download_data->length())); +#endif + handler()->OnReceivedPassed(this, download_data.Pass()); } } NetFetcher::~NetFetcher() { DCHECK(thread_checker_.CalledOnValidThread()); - start_callback_.Cancel(); + csp_reject_callback_.Cancel(); } NetFetcher::ReturnWrapper NetFetcher::HandleError(const std::string& message) {
diff --git a/src/cobalt/loader/net_fetcher.h b/src/cobalt/loader/net_fetcher.h index ce816b5..1d6bc5d 100644 --- a/src/cobalt/loader/net_fetcher.h +++ b/src/cobalt/loader/net_fetcher.h
@@ -59,7 +59,7 @@ net::URLFetcher* url_fetcher() const { return url_fetcher_.get(); } private: - void Start(); + void ProcessCSPReject(); // Empty struct to ensure the caller of |HandleError()| knows that |this| // may have been destroyed and handles it appropriately. @@ -81,9 +81,9 @@ base::ThreadChecker thread_checker_; scoped_ptr<net::URLFetcher> url_fetcher_; csp::SecurityCallback security_callback_; - // Ensure we can cancel any in-flight Start() task if we are destroyed - // after being constructed, but before Start() runs. - base::CancelableClosure start_callback_; + // Ensure we can cancel any in-flight ProcessCSPReject() task if we are + // destroyed after being constructed, but before ProcessCSPReject() runs. + base::CancelableClosure csp_reject_callback_; DISALLOW_COPY_AND_ASSIGN(NetFetcher); };
diff --git a/src/cobalt/loader/resource_cache.h b/src/cobalt/loader/resource_cache.h index 2f4ce23..1c3e4e6 100644 --- a/src/cobalt/loader/resource_cache.h +++ b/src/cobalt/loader/resource_cache.h
@@ -47,6 +47,13 @@ template <typename CacheType> class ResourceCache; +enum CallbackType { + kOnLoadingSuccessCallbackType, + kOnLoadingFailureCallbackType, + kOnLoadingErrorCallbackType, + kCallbackTypeCount, +}; + ////////////////////////////////////////////////////////////////////////// // CachedResource - Declarations ////////////////////////////////////////////////////////////////////////// @@ -121,17 +128,11 @@ private: friend class base::RefCountedThreadSafe<CachedResource>; friend class OnLoadedCallbackHandler; + friend class ResourceCache<CacheType>; typedef std::list<base::Closure> CallbackList; typedef std::list<base::Closure>::iterator CallbackListIterator; - enum CallbackType { - kOnLoadingSuccessCallbackType, - kOnLoadingFailureCallbackType, - kOnLoadingErrorCallbackType, - kCallbackTypeCount, - }; - ~CachedResource(); // Callbacks for decoders. @@ -274,10 +275,8 @@ resource_ = resource; loader_.reset(); - resource_cache_->NotifyResourceSuccessfullyLoaded(this); - // To avoid the last reference of this object get deleted in the callbacks. - scoped_refptr<CachedResource<CacheType> > holder(this); - RunCallbacks(kOnLoadingSuccessCallbackType); + resource_cache_->NotifyResourceLoadingComplete(this, + kOnLoadingSuccessCallbackType); } template <typename CacheType> @@ -287,9 +286,8 @@ LOG(WARNING) << "Warning while loading '" << url_ << "': " << message; loader_.reset(); - // To avoid the last reference of this object get deleted in the callbacks. - scoped_refptr<CachedResource<CacheType> > holder(this); - RunCallbacks(kOnLoadingFailureCallbackType); + resource_cache_->NotifyResourceLoadingComplete(this, + kOnLoadingFailureCallbackType); } template <typename CacheType> @@ -299,9 +297,8 @@ LOG(ERROR) << "Error while loading '" << url_ << "': " << error; loader_.reset(); - // To avoid the last reference of this object get deleted in the callbacks. - scoped_refptr<CachedResource<CacheType> > holder(this); - RunCallbacks(kOnLoadingErrorCallbackType); + resource_cache_->NotifyResourceLoadingComplete(this, + kOnLoadingErrorCallbackType); } template <typename CacheType> @@ -328,7 +325,7 @@ void CachedResource<CacheType>::RunCallbacks(CallbackType type) { DCHECK(cached_resource_thread_checker_.CalledOnValidThread()); - // To avoid the list gets altered in the callbacks. + // To avoid the list getting altered in the callbacks. CallbackList callback_list = callback_lists[type]; CallbackListIterator callback_iter; for (callback_iter = callback_list.begin(); @@ -391,6 +388,15 @@ typedef typename CachedResourceType::CreateLoaderFunction CreateLoaderFunction; + struct ResourceCallbackInfo { + ResourceCallbackInfo(CachedResourceType* cached_resource, + CallbackType callback_type) + : cached_resource(cached_resource), callback_type(callback_type) {} + + CachedResourceType* cached_resource; + CallbackType callback_type; + }; + ResourceCache(const std::string& name, uint32 cache_capacity, const CreateLoaderFunction& create_loader_function); @@ -421,12 +427,17 @@ typedef base::hash_map<std::string, CachedResourceType*> CachedResourceMap; typedef typename CachedResourceMap::iterator CachedResourceMapIterator; + typedef base::hash_set<std::string> ResourceSet; + typedef base::linked_hash_map<std::string, ResourceCallbackInfo> + ResourceCallbackMap; + typedef base::linked_hash_map<std::string, scoped_refptr<ResourceType> > ResourceMap; typedef typename ResourceMap::iterator ResourceMapIterator; - // Called by CachedResource objects after they are successfully loaded. - void NotifyResourceSuccessfullyLoaded(CachedResourceType* cached_resource); + // Called by CachedResource objects after they finish loading. + void NotifyResourceLoadingComplete(CachedResourceType* cached_resource, + CallbackType callback_type); // Called by the destructor of CachedResource to remove CachedResource from // |cached_resource_map_| and either immediately free the resource from memory @@ -434,10 +445,24 @@ // cache is over its memory limit. void NotifyResourceDestroyed(CachedResourceType* cached_resource); + // Reclaims memory from unreferenced cache objects until total cache memory + // is reduced to |bytes_to_reclaim_down_to|. In the case where the desired + // memory cannot be freed, pending callbacks are processed (potentially + // enabling additional resources to be reclaimed), and memory reclamation is + // attempted again. + void ReclaimMemoryAndMaybeProcessPendingCallbacks( + uint32 bytes_to_reclaim_down_to); // Releases unreferenced cache objects until our total cache memory usage is // less than or equal to |bytes_to_reclaim_down_to|, or until there are no // more unreferenced cache objects to release. - void ReclaimMemory(uint32 bytes_to_reclaim_down_to); + void ReclaimMemory(uint32 bytes_to_reclaim_down_to, bool log_warning_if_over); + + // Calls ProcessPendingCallbacks() if + // |callback_blocking_loading_resource_set_| is empty. + void ProcessPendingCallbacksIfUnblocked(); + // Processes all pending callbacks regardless of the state of + // |callback_blocking_loading_resource_set_|. + void ProcessPendingCallbacks(); // The name of this resource cache object, useful while debugging. const std::string name_; @@ -448,6 +473,28 @@ csp::SecurityCallback security_callback_; + // The resource cache attempts to batch callbacks as much as possible to try + // to ensure that events triggered by the callbacks occur together. It + // accomplishes this by waiting for all active loads to complete before + // processing any of their callbacks. However, to ensure that callbacks are + // processed in a timely manner as well, active loads are placed into two + // buckets: callback blocking and non-callback blocking. While no callbacks + // are pending, all active loads are added as callback blocking. As soon as + // a callback is pending, any additional load requests are added as + // non-callback blocking. As soon as all of the callback blocking loads are + // finished, the pending callbacks are processed, the non-callback blocking + // loads become callback blocking loads, and the process repeats itself. + + // Currently loading resources that block any pending callbacks from running. + ResourceSet callback_blocking_loading_resource_set_; + // Currently loading resources that do not block the pending callbacks from + // running. After pending callbacks run, these become blocking. + ResourceSet non_callback_blocking_loading_resource_set_; + // Resources that have completed loading and have callbacks pending. + ResourceCallbackMap pending_callback_map_; + // Whether or not ProcessPendingCallbacks() is running. + bool is_processing_pending_callbacks_; + // |cached_resource_map_| stores the cached resources that are currently // referenced. CachedResourceMap cached_resource_map_; @@ -476,6 +523,7 @@ : name_(name), cache_capacity_(cache_capacity), create_loader_function_(create_loader_function), + is_processing_pending_callbacks_(false), size_in_bytes_(base::StringPrintf("%s.Size", name_.c_str()), 0, "Total number of bytes currently used by the cache."), capacity_in_bytes_(base::StringPrintf("%s.Capacity", name_.c_str()), @@ -512,8 +560,21 @@ return cached_resource; } - // If the resource doesn't exist, create a cached resource and fetch the - // resource based on the url. + // If we reach this point, then the resource doesn't exist yet. + + // Add the resource to a loading set. If no current resources have pending + // callbacks, then this resource will block callbacks until it is decoded. + // However, if there are resources with pending callbacks, then the decoding + // of this resource won't block the callbacks from occurring. This ensures + // that a steady stream of new resources won't prevent callbacks from ever + // occurring. + if (pending_callback_map_.empty()) { + callback_blocking_loading_resource_set_.insert(url.spec()); + } else { + non_callback_blocking_loading_resource_set_.insert(url.spec()); + } + + // Create the cached resource and fetch its resource based on the url. scoped_refptr<CachedResourceType> cached_resource(new CachedResourceType( url, security_callback_, create_loader_function_, this)); cached_resource_map_.insert( @@ -526,55 +587,106 @@ DCHECK(resource_cache_thread_checker_.CalledOnValidThread()); cache_capacity_ = capacity; capacity_in_bytes_ = capacity; - ReclaimMemory(cache_capacity_); + ReclaimMemoryAndMaybeProcessPendingCallbacks(cache_capacity_); } template <typename CacheType> void ResourceCache<CacheType>::Purge() { DCHECK(resource_cache_thread_checker_.CalledOnValidThread()); - ReclaimMemory(0); + ReclaimMemoryAndMaybeProcessPendingCallbacks(0); } template <typename CacheType> -void ResourceCache<CacheType>::NotifyResourceSuccessfullyLoaded( - CachedResourceType* cached_resource) { +void ResourceCache<CacheType>::NotifyResourceLoadingComplete( + CachedResourceType* cached_resource, CallbackType callback_type) { DCHECK(resource_cache_thread_checker_.CalledOnValidThread()); + const std::string& url = cached_resource->url().spec(); if (cached_resource->TryGetResource()) { size_in_bytes_ += CacheType::GetEstimatedSizeInBytes(cached_resource->TryGetResource()); - if (size_in_bytes_ > cache_capacity_) { - ReclaimMemory(cache_capacity_); - } } + + // Remove the resource from its loading set. It should exist in exactly one + // of the loading sets. + if (callback_blocking_loading_resource_set_.erase(url)) { + DCHECK(non_callback_blocking_loading_resource_set_.find(url) == + non_callback_blocking_loading_resource_set_.end()); + } else if (!non_callback_blocking_loading_resource_set_.erase(url)) { + DCHECK(false); + } + + // Add a callback for the resource that just finished loading to the pending + // callbacks. + pending_callback_map_.insert(std::make_pair( + url, ResourceCallbackInfo(cached_resource, callback_type))); + + ProcessPendingCallbacksIfUnblocked(); + ReclaimMemoryAndMaybeProcessPendingCallbacks(cache_capacity_); } template <typename CacheType> void ResourceCache<CacheType>::NotifyResourceDestroyed( CachedResourceType* cached_resource) { DCHECK(resource_cache_thread_checker_.CalledOnValidThread()); + const std::string& url = cached_resource->url().spec(); - std::string url = cached_resource->url().spec(); cached_resource_map_.erase(url); DCHECK(unreference_cached_resource_map_.find(url) == unreference_cached_resource_map_.end()); + // Check to see if this was a loaded resource. if (cached_resource->TryGetResource()) { // Add it into the unreferenced cached resource map, so that it will be // retained while memory is available for it in the cache. unreference_cached_resource_map_.insert( std::make_pair(url, cached_resource->TryGetResource())); - // Try to reclaim some memory. - ReclaimMemory(cache_capacity_); + } + + // Remove the resource from any loading or pending container that it is in. + // It should never exist in more than one of the containers. + if (callback_blocking_loading_resource_set_.erase(url)) { + DCHECK(non_callback_blocking_loading_resource_set_.find(url) == + non_callback_blocking_loading_resource_set_.end()); + DCHECK(pending_callback_map_.find(url) == pending_callback_map_.end()); + } else if (non_callback_blocking_loading_resource_set_.erase(url)) { + DCHECK(pending_callback_map_.find(url) == pending_callback_map_.end()); + } else { + pending_callback_map_.erase(url); + } + + // Only process pending callbacks and attempt to reclaim memory if + // NotifyResourceDestroyed() wasn't called from within + // ProcessPendingCallbacks(). This prevents recursion and redundant + // processing. + if (!is_processing_pending_callbacks_) { + ProcessPendingCallbacksIfUnblocked(); + ReclaimMemory(cache_capacity_, true /*log_warning_if_over*/); } } template <typename CacheType> -void ResourceCache<CacheType>::ReclaimMemory(uint32 bytes_to_reclaim_down_to) { +void ResourceCache<CacheType>::ReclaimMemoryAndMaybeProcessPendingCallbacks( + uint32 bytes_to_reclaim_down_to) { + ReclaimMemory(bytes_to_reclaim_down_to, false /*log_warning_if_over*/); + // If the current size of the cache is still greater than + // |bytes_to_reclaim_down_to| after reclaiming memory, then process any + // pending callbacks and try again. References to the cached resources are + // potentially being held until the callbacks run, so processing them may + // enable more memory to be reclaimed. + if (size_in_bytes_ > bytes_to_reclaim_down_to) { + ProcessPendingCallbacks(); + ReclaimMemory(bytes_to_reclaim_down_to, true /*log_warning_if_over*/); + } +} + +template <typename CacheType> +void ResourceCache<CacheType>::ReclaimMemory(uint32 bytes_to_reclaim_down_to, + bool log_warning_if_over) { DCHECK(resource_cache_thread_checker_.CalledOnValidThread()); - while (size_in_bytes_.value() > bytes_to_reclaim_down_to && + while (size_in_bytes_ > bytes_to_reclaim_down_to && !unreference_cached_resource_map_.empty()) { // The first element is the earliest-inserted element. scoped_refptr<ResourceType> resource = @@ -588,14 +700,47 @@ size_in_bytes_ -= first_resource_size; } - // Make sure that |size_in_bytes_| is less than or equal to |cache_capacity_|, - // otherwise it means that |unreference_cached_resource_map_| is empty. We - // have to increase the size of |cache_capacity_| if the system memory is - // large enough or evict resources from the cache even though they are still - // in use. - DLOG_IF(WARNING, size_in_bytes_.value() > cache_capacity_) - << "cached size: " << size_in_bytes_ - << ", cache capacity: " << cache_capacity_; + if (log_warning_if_over) { + // Log a warning if we're still over |bytes_to_reclaim_down_to| after + // attempting to reclaim memory. This can occur validly when the size of + // the referenced images exceeds the target size. + DLOG_IF(WARNING, size_in_bytes_ > bytes_to_reclaim_down_to) + << "cached size: " << size_in_bytes_ + << ", target size: " << bytes_to_reclaim_down_to; + } +} + +template <typename CacheType> +void ResourceCache<CacheType>::ProcessPendingCallbacksIfUnblocked() { + if (callback_blocking_loading_resource_set_.empty()) { + ProcessPendingCallbacks(); + + // Now that we've processed the callbacks, if there are any non-blocking + // loading resources, then they're becoming blocking. Simply swap the two + // sets, rather than copying the contents over. + if (!non_callback_blocking_loading_resource_set_.empty()) { + callback_blocking_loading_resource_set_.swap( + non_callback_blocking_loading_resource_set_); + } + } +} + +template <typename CacheType> +void ResourceCache<CacheType>::ProcessPendingCallbacks() { + DCHECK(resource_cache_thread_checker_.CalledOnValidThread()); + + is_processing_pending_callbacks_ = true; + while (!pending_callback_map_.empty()) { + ResourceCallbackInfo& callback_info = pending_callback_map_.front().second; + + // To avoid the last reference of this object getting deleted in the + // callbacks. + scoped_refptr<CachedResourceType> holder(callback_info.cached_resource); + callback_info.cached_resource->RunCallbacks(callback_info.callback_type); + + pending_callback_map_.erase(pending_callback_map_.begin()); + } + is_processing_pending_callbacks_ = false; } } // namespace loader
diff --git a/src/cobalt/loader/sync_loader.cc b/src/cobalt/loader/sync_loader.cc index 1d0d116..7ceff82 100644 --- a/src/cobalt/loader/sync_loader.cc +++ b/src/cobalt/loader/sync_loader.cc
@@ -18,6 +18,7 @@ #include "base/bind.h" #include "base/compiler_specific.h" +#include "base/debug/trace_event.h" #include "base/synchronization/waitable_event.h" namespace cobalt { @@ -117,6 +118,7 @@ base::Callback<scoped_ptr<Fetcher>(Fetcher::Handler*)> fetcher_creator, base::Callback<scoped_ptr<Decoder>()> decoder_creator, base::Callback<void(const std::string&)> error_callback) { + TRACE_EVENT0("cobalt::loader", "LoadSynchronously()"); DCHECK(message_loop); DCHECK(!error_callback.is_null());
diff --git a/src/cobalt/loader/text_decoder.h b/src/cobalt/loader/text_decoder.h index 5de3c4c..dd191d7 100644 --- a/src/cobalt/loader/text_decoder.h +++ b/src/cobalt/loader/text_decoder.h
@@ -17,11 +17,13 @@ #ifndef COBALT_LOADER_TEXT_DECODER_H_ #define COBALT_LOADER_TEXT_DECODER_H_ +#include <algorithm> #include <string> #include "base/callback.h" #include "base/compiler_specific.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/threading/thread_checker.h" #include "cobalt/loader/decoder.h" @@ -50,6 +52,21 @@ } text_.append(data, size); } + + void DecodeChunkPassed(scoped_ptr<std::string> data) OVERRIDE { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(data); + if (suspended_) { + return; + } + + if (text_.empty()) { + std::swap(*data, text_); + } else { + text_.append(*data); + } + } + void Finish() OVERRIDE { DCHECK(thread_checker_.CalledOnValidThread()); if (suspended_) {
diff --git a/src/cobalt/math/cubic_bezier.cc b/src/cobalt/math/cubic_bezier.cc index 17c8368..51b5df0 100644 --- a/src/cobalt/math/cubic_bezier.cc +++ b/src/cobalt/math/cubic_bezier.cc
@@ -66,8 +66,10 @@ } // We should have terminated the above loop because we got close to x, not - // because we exceeded MAX_STEPS. Do a DCHECK here to confirm. - DCHECK_GT(kBezierEpsilon, std::abs(eval_bezier(x1, x2, t) - x)); + // because we exceeded MAX_STEPS. Warn if this is not the case. + if (std::abs(eval_bezier(x1, x2, t) - x) > kBezierEpsilon) { + DLOG(WARNING) << "Notable error detected in bezier evaluation."; + } return t; }
diff --git a/src/cobalt/media/media.gyp b/src/cobalt/media/media.gyp index 70c6929..ed7dc27 100644 --- a/src/cobalt/media/media.gyp +++ b/src/cobalt/media/media.gyp
@@ -35,6 +35,7 @@ 'dependencies': [ '<(DEPTH)/cobalt/network/network.gyp:network', '<(DEPTH)/media/media.gyp:media', + '<(DEPTH)/nb/nb.gyp:nb', ], 'export_dependent_settings': [ '<(DEPTH)/media/media.gyp:media', @@ -46,9 +47,6 @@ 'shell_media_platform_<(sb_media_platform).cc', 'shell_media_platform_<(sb_media_platform).h', ], - 'dependencies': [ - '<(DEPTH)/nb/nb.gyp:nb', - ], }], ['OS=="starboard" and sb_media_platform == "ps4"', { 'sources': [ @@ -72,9 +70,6 @@ 'shell_media_platform_ps3.cc', 'shell_media_platform_ps3.h', ], - 'dependencies': [ - '<(DEPTH)/nb/nb.gyp:nb', - ], }], ], },
diff --git a/src/cobalt/media/media_buffer_allocator.h b/src/cobalt/media/media_buffer_allocator.h new file mode 100644 index 0000000..ef7f620 --- /dev/null +++ b/src/cobalt/media/media_buffer_allocator.h
@@ -0,0 +1,89 @@ +/* + * Copyright 2016 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_MEDIA_MEDIA_BUFFER_ALLOCATOR_H_ +#define COBALT_MEDIA_MEDIA_BUFFER_ALLOCATOR_H_ + +#include "base/optional.h" +#include "nb/memory_pool.h" + +namespace cobalt { +namespace media { + +class MediaBufferAllocator { + public: + MediaBufferAllocator(void* pool, size_t main_pool_size, + size_t small_allocation_pool_size, + size_t small_allocation_threshold) + : pool_(reinterpret_cast<uint8*>(pool)), + main_pool_size_(main_pool_size), + small_allocation_pool_size_(small_allocation_pool_size), + small_allocation_threshold_(small_allocation_threshold), + main_pool_(pool_, main_pool_size_, true, /* thread_safe */ + true /* verify_full_capacity */) { + if (small_allocation_pool_size_ > 0u) { + DCHECK_GT(small_allocation_threshold_, 0u); + small_allocation_pool_.emplace(pool_ + main_pool_size_, + small_allocation_pool_size_, + true, /* thread_safe */ + true /* verify_full_capacity */); + } else { + DCHECK_EQ(small_allocation_pool_size_, 0u); + DCHECK_EQ(small_allocation_threshold_, 0u); + } + } + + void* Allocate(size_t size, size_t alignment) { + void* p = NULL; + if (size < small_allocation_threshold_ && small_allocation_pool_) { + p = small_allocation_pool_->Allocate(size, alignment); + } + if (!p) { + p = main_pool_.Allocate(size, alignment); + } + if (!p && small_allocation_pool_) { + p = small_allocation_pool_->Allocate(size, alignment); + } + return p; + } + + void Free(void* p) { + if (p >= pool_ + main_pool_size_ && small_allocation_pool_) { + DCHECK_LT(p, pool_ + main_pool_size_ + small_allocation_pool_size_); + small_allocation_pool_->Free(p); + return; + } + DCHECK_GE(p, pool_); + DCHECK_LT(p, pool_ + main_pool_size_); + main_pool_.Free(p); + } + + private: + uint8* pool_; + size_t main_pool_size_; + size_t small_allocation_pool_size_; + size_t small_allocation_threshold_; + + nb::MemoryPool main_pool_; + base::optional<nb::MemoryPool> small_allocation_pool_; + + DISALLOW_COPY_AND_ASSIGN(MediaBufferAllocator); +}; + +} // namespace media +} // namespace cobalt + +#endif // COBALT_MEDIA_MEDIA_BUFFER_ALLOCATOR_H_
diff --git a/src/cobalt/media/media_module_starboard.cc b/src/cobalt/media/media_module_starboard.cc index 4c54544..f4cf154 100644 --- a/src/cobalt/media/media_module_starboard.cc +++ b/src/cobalt/media/media_module_starboard.cc
@@ -16,21 +16,14 @@ #include "cobalt/media/media_module.h" -#include "base/bind.h" #include "base/compiler_specific.h" #include "cobalt/base/polymorphic_downcast.h" #include "cobalt/math/size.h" #include "cobalt/media/shell_media_platform_starboard.h" #include "cobalt/system_window/starboard/system_window.h" -#include "media/audio/null_audio_streamer.h" -#include "media/audio/shell_audio_sink.h" #include "media/base/filter_collection.h" #include "media/base/media_log.h" #include "media/base/message_loop_factory.h" -#include "media/filters/shell_audio_decoder_impl.h" -#include "media/filters/shell_raw_audio_decoder_stub.h" -#include "media/filters/shell_raw_video_decoder_stub.h" -#include "media/filters/shell_video_decoder_impl.h" #include "media/player/web_media_player_impl.h" #include "starboard/media.h" #include "starboard/window.h" @@ -76,14 +69,6 @@ message_loop_factory->GetMessageLoop(MessageLoopFactory::kPipeline); scoped_ptr<FilterCollection> filter_collection(new FilterCollection); - ::media::ShellAudioStreamer* streamer = NULL; - if (options_.use_null_audio_streamer) { - DLOG(INFO) << "Use Null audio"; - streamer = ::media::NullAudioStreamer::GetInstance(); - } else { - DLOG(INFO) << "Use Pulse audio"; - streamer = ::media::ShellAudioStreamer::Instance(); - } SbWindow window = kSbWindowInvalid; if (system_window_) { window = polymorphic_downcast<SystemWindowStarboard*>(system_window_) @@ -91,8 +76,8 @@ } return make_scoped_ptr<WebMediaPlayer>(new ::media::WebMediaPlayerImpl( window, client, this, media_platform_.GetVideoFrameProvider(), - filter_collection.Pass(), new ::media::ShellAudioSink(streamer), - message_loop_factory.Pass(), new ::media::MediaLog)); + filter_collection.Pass(), NULL, message_loop_factory.Pass(), + new ::media::MediaLog)); } private:
diff --git a/src/cobalt/media/shell_media_platform_starboard.cc b/src/cobalt/media/shell_media_platform_starboard.cc index 096b670..9b9bf31 100644 --- a/src/cobalt/media/shell_media_platform_starboard.cc +++ b/src/cobalt/media/shell_media_platform_starboard.cc
@@ -38,6 +38,8 @@ const size_t kGPUMemoryBufferBudget = SB_MEDIA_GPU_BUFFER_BUDGET; const size_t kMainMemoryBufferBudget = SB_MEDIA_MAIN_BUFFER_BUDGET; +const size_t kSmallAllocationThreshold = 768U; + } // namespace ShellMediaPlatformStarboard::ShellMediaPlatformStarboard( @@ -58,7 +60,7 @@ gpu_memory_pool_.reset(new nb::MemoryPool( gpu_memory_buffer_space_->GetMemory(), gpu_memory_buffer_space_->GetSizeInBytes(), true, /* thread_safe */ - true /* verify_full_capacity */)); + true /* verify_full_capacity */, kSmallAllocationThreshold)); } DCHECK_LE(0, kMainMemoryBufferBudget > 0); @@ -68,7 +70,8 @@ main_memory_pool_.reset(new nb::MemoryPool(main_memory_buffer_space_.get(), kMainMemoryBufferBudget, true, /* thread_safe */ - true /* verify_full_capacity */)); + true, /* verify_full_capacity */ + kSmallAllocationThreshold)); ShellBufferFactory::Initialize(); ShellAudioStreamer::Initialize();
diff --git a/src/cobalt/network/persistent_cookie_store.cc b/src/cobalt/network/persistent_cookie_store.cc index a5dd276..ce0926c 100644 --- a/src/cobalt/network/persistent_cookie_store.cc +++ b/src/cobalt/network/persistent_cookie_store.cc
@@ -55,9 +55,13 @@ get_all.ColumnString(6), base::Time::FromInternalValue(get_all.ColumnInt64(7)), expiry, get_all.ColumnBool(10), get_all.ColumnBool(11)); - cookie->SetLastAccessDate( - base::Time::FromInternalValue(get_all.ColumnInt64(9))); - actual_cookies.push_back(cookie); + if (cookie) { + cookie->SetLastAccessDate( + base::Time::FromInternalValue(get_all.ColumnInt64(9))); + actual_cookies.push_back(cookie); + } else { + DLOG(ERROR) << "Failed to create cookie."; + } } return actual_cookies;
diff --git a/src/cobalt/render_tree/animations/animate_node.cc b/src/cobalt/render_tree/animations/animate_node.cc index 6363ec9..0663359 100644 --- a/src/cobalt/render_tree/animations/animate_node.cc +++ b/src/cobalt/render_tree/animations/animate_node.cc
@@ -21,6 +21,7 @@ #include "base/debug/trace_event.h" #include "cobalt/base/enable_if.h" #include "cobalt/base/polymorphic_downcast.h" +#include "cobalt/math/transform_2d.h" #include "cobalt/render_tree/child_iterator.h" #include "cobalt/render_tree/node_visitor.h" @@ -214,6 +215,158 @@ animated_ = true; } +// A helper class for computing the tightest bounding rectangle that encloses +// all animated subtrees. It is meant to be applied to an already-animated +// render tree, using a TraverseList provided by +// ApplyVisitor::animated_traverse_list(). The result of the visit can be +// retrieved from bounds(). This calculation is useful for determining a +// "dirty rectangle" to minimize drawing. +class AnimateNode::BoundsVisitor : public NodeVisitor { + public: + BoundsVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset, + base::TimeDelta since); + + void Visit(animations::AnimateNode* /* animate */) OVERRIDE { + // An invariant of AnimateNodes is that they should never contain descendant + // AnimateNodes. + NOTREACHED(); + } + // Immediately switch to a templated visitor function. + void Visit(CompositionNode* composition) OVERRIDE { VisitNode(composition); } + void Visit(FilterNode* text) OVERRIDE { VisitNode(text); } + void Visit(ImageNode* image) OVERRIDE { VisitNode(image); } + void Visit(MatrixTransformNode* transform) OVERRIDE { VisitNode(transform); } + void Visit(PunchThroughVideoNode* punch_through) OVERRIDE { + VisitNode(punch_through); + } + void Visit(RectNode* rect) OVERRIDE { VisitNode(rect); } + void Visit(RectShadowNode* rect) OVERRIDE { VisitNode(rect); } + void Visit(TextNode* text) OVERRIDE { VisitNode(text); } + + const math::RectF& bounds() const { return bounds_; } + + private: + template <typename T> + typename base::enable_if<!ChildIterator<T>::has_children>::type VisitNode( + T* node); + template <typename T> + typename base::enable_if<ChildIterator<T>::has_children>::type VisitNode( + T* node); + void ProcessAnimatedNodeBounds(const TraverseListEntry& entry, + render_tree::Node* node); + TraverseListEntry AdvanceIterator(Node* node); + + void ApplyTransform(Node* node); + void ApplyTransform(CompositionNode* node); + void ApplyTransform(MatrixTransformNode* node); + + // The time offset to be passed in to individual animations. + base::TimeDelta time_offset_; + + // The time when we "start" checking for active animations. In other words, + // if an animation had expired *before* |since_|, then it is not considered + // animated, and not considered for the bounding box. + base::TimeDelta since_; + + // A list of nodes that we are allowed to traverse into (i.e. a traversal that + // guides us to animated nodes). It assumes that a pre-order traversal will + // be taken. + const TraverseList& traverse_list_; + + // An iterator pointing to the next valid render tree node to visit. + TraverseList::const_iterator iterator_; + + // The resulting bounding box surrounding all active animations. + math::RectF bounds_; + + // We need to maintain a "current" transform as we traverse the tree, so that + // we know the transformed bounding boxes of nodes when we reach them. + math::Matrix3F transform_; +}; + +AnimateNode::BoundsVisitor::BoundsVisitor(const TraverseList& traverse_list, + base::TimeDelta time_offset, + base::TimeDelta since) + : time_offset_(time_offset), + since_(since), + traverse_list_(traverse_list), + transform_(math::Matrix3F::Identity()) { + iterator_ = traverse_list_.begin(); +} + +template <typename T> +typename base::enable_if<!ChildIterator<T>::has_children>::type +AnimateNode::BoundsVisitor::VisitNode(T* node) { + TraverseListEntry current_entry = AdvanceIterator(node); + + DCHECK(current_entry.animations); + ProcessAnimatedNodeBounds(current_entry, node); +} + +template <typename T> +typename base::enable_if<ChildIterator<T>::has_children>::type +AnimateNode::BoundsVisitor::VisitNode(T* node) { + TraverseListEntry current_entry = AdvanceIterator(node); + + math::Matrix3F old_transform = transform_; + ApplyTransform(node); + + // Traverse the child nodes, but only the ones that are on the + // |traverse_list_|. In particular, the next node we are allowed to visit + // is the one in the traverse list pointed to by |iterator_->node|. + ChildIterator<T> child_iterator(node); + while (Node* child = child_iterator.GetCurrent()) { + if (iterator_ == traverse_list_.end()) { + // If we've reached the end of |traverse_list_| then we are done + // iterating and it's time to return. + break; + } + + if (child == iterator_->node) { + // If one of our children is next up on the path to animation, traverse + // into it. + child->Accept(this); + } + + child_iterator.Next(); + } + transform_ = old_transform; + + if (current_entry.animations) { + ProcessAnimatedNodeBounds(current_entry, node); + } +} + +void AnimateNode::BoundsVisitor::ProcessAnimatedNodeBounds( + const TraverseListEntry& entry, render_tree::Node* node) { + TRACE_EVENT0("cobalt::renderer", + "AnimateNode::BoundsVisitor::ProcessAnimatedNodeBounds()"); + if (entry.animations->GetExpiry() >= since_) { + bounds_.Union(transform_.MapRect(node->GetBounds())); + } +} + +AnimateNode::TraverseListEntry AnimateNode::BoundsVisitor::AdvanceIterator( + Node* node) { + // Check that the iterator that we are advancing past is indeed the one we + // expect it to be. + DCHECK_EQ(node, iterator_->node); + return *(iterator_++); +} + +void AnimateNode::BoundsVisitor::ApplyTransform(Node* node) { + UNREFERENCED_PARAMETER(node); +} + +void AnimateNode::BoundsVisitor::ApplyTransform(CompositionNode* node) { + transform_ = transform_ * math::TranslateMatrix(node->data().offset().x(), + node->data().offset().y()); +} + +void AnimateNode::BoundsVisitor::ApplyTransform(MatrixTransformNode* node) { + transform_ = transform_ * node->data().transform; +} + // A helper render tree visitor class used to apply compiled sub render-tree // animations. Only one of these visitors is needed to visit an entire render // tree. @@ -242,6 +395,11 @@ // final animated result can be pulled from this visitor. const scoped_refptr<Node>& animated() const { return animated_; } + // As we compute the animated nodes, we create a new traverse list that leads + // to the newly created animated nodes. This can be used afterwards to + // calculate the bounding boxes around the active animated nodes. + TraverseList* animated_traverse_list() { return &animated_traverse_list_; } + private: template <typename T> typename base::enable_if<!ChildIterator<T>::has_children>::type VisitNode( @@ -250,8 +408,8 @@ typename base::enable_if<ChildIterator<T>::has_children>::type VisitNode( T* node); template <typename T> - void ApplyAnimations(const TraverseListEntry& entry, - typename T::Builder* builder); + scoped_refptr<Node> ApplyAnimations(const TraverseListEntry& entry, + typename T::Builder* builder); TraverseListEntry AdvanceIterator(Node* node); // The time offset to be passed in to individual animations. @@ -262,6 +420,12 @@ // guides us to animated nodes). It assumes that a pre-order traversal will // be taken. const TraverseList& traverse_list_; + + // As we animate the nodes, we also keep track of a new traverse list that + // replaces the non-animated nodes for the animated nodes, so that we can + // go through and traverse the animated nodes after they have been animated. + TraverseList animated_traverse_list_; + // An iterator pointing to the next valid render tree node to visit. TraverseList::const_iterator iterator_; }; @@ -269,6 +433,7 @@ AnimateNode::ApplyVisitor::ApplyVisitor(const TraverseList& traverse_list, base::TimeDelta time_offset) : time_offset_(time_offset), traverse_list_(traverse_list) { + animated_traverse_list_.reserve(traverse_list.size()); iterator_ = traverse_list_.begin(); } @@ -280,8 +445,9 @@ // have animations. DCHECK(current_entry.animations); typename T::Builder builder(node->data()); - ApplyAnimations<T>(current_entry, &builder); - animated_ = new T(builder); + animated_ = ApplyAnimations<T>(current_entry, &builder); + animated_traverse_list_.push_back( + TraverseListEntry(animated_, current_entry.animations)); } template <typename T> @@ -289,6 +455,10 @@ AnimateNode::ApplyVisitor::VisitNode(T* node) { TraverseListEntry current_entry = AdvanceIterator(node); + size_t animated_traverse_list_index = animated_traverse_list_.size(); + animated_traverse_list_.push_back( + TraverseListEntry(NULL, current_entry.animations)); + // Traverse the child nodes, but only the ones that are on the // |traverse_list_|. In particular, the next node we are allowed to visit // is the one in the traverse list pointed to by |iterator_->node|. @@ -326,8 +496,7 @@ // be passed into the animations. builder.emplace(node->data()); } - ApplyAnimations<T>(current_entry, &(*builder)); - animated_ = new T(*builder); + animated_ = ApplyAnimations<T>(current_entry, &(*builder)); } else { // If there were no animations targeting this node directly, then its // children must have been modified since otherwise it wouldn't be in @@ -335,11 +504,13 @@ DCHECK(children_modified); animated_ = new T(child_iterator.TakeReplacedChildrenBuilder()); } + + animated_traverse_list_[animated_traverse_list_index].node = animated_; } template <typename T> -void AnimateNode::ApplyVisitor::ApplyAnimations(const TraverseListEntry& entry, - typename T::Builder* builder) { +scoped_refptr<Node> AnimateNode::ApplyVisitor::ApplyAnimations( + const TraverseListEntry& entry, typename T::Builder* builder) { TRACE_EVENT0("cobalt::renderer", "AnimateNode::ApplyVisitor::ApplyAnimations()"); // Cast to the specific type we expect these animations to have. @@ -353,6 +524,8 @@ iter != typed_node_animations->data().animations.end(); ++iter) { iter->Run(builder, time_offset_); } + + return new T(*builder); } AnimateNode::TraverseListEntry AnimateNode::ApplyVisitor::AdvanceIterator( @@ -374,15 +547,66 @@ CommonInit(Builder::InternalMap(), source); } -scoped_refptr<Node> AnimateNode::Apply(base::TimeDelta time_offset) { +// Helper class to refcount wrap a TraverseList object so that it can be +// passed around in a callback. +class AnimateNode::RefCountedTraversalList + : public base::RefCounted<RefCountedTraversalList> { + public: + explicit RefCountedTraversalList(TraverseList* to_swap_in) { + traverse_list_.swap(*to_swap_in); + } + const TraverseList& traverse_list() const { return traverse_list_; } + + private: + friend class base::RefCounted<RefCountedTraversalList>; + ~RefCountedTraversalList() {} + + TraverseList traverse_list_; +}; + +// static +math::RectF AnimateNode::GetAnimationBoundsSince( + const scoped_refptr<RefCountedTraversalList>& traverse_list, + base::TimeDelta time_offset, const scoped_refptr<Node>& animated, + base::TimeDelta since) { + TRACE_EVENT0("cobalt::renderer", "AnimateNode::GetAnimationBoundsSince()"); + + BoundsVisitor bounds_visitor(traverse_list->traverse_list(), time_offset, + since); + animated->Accept(&bounds_visitor); + return bounds_visitor.bounds(); +} + +namespace { +// Helper function to always return an empty bounding rectangle. +math::RectF ReturnTrivialEmptyRectBound(base::TimeDelta since) { + UNREFERENCED_PARAMETER(since); + return math::RectF(); +} +} // namespace + +AnimateNode::AnimateResults AnimateNode::Apply(base::TimeDelta time_offset) { TRACE_EVENT0("cobalt::renderer", "AnimateNode::Apply()"); + AnimateResults results; if (traverse_list_.empty()) { - return source_; + results.animated = source_; + // There are no animations, so there is no bounding rectangle, so setup the + // bounding box function to trivially return an empty rectangle. + results.get_animation_bounds_since = + base::Bind(&ReturnTrivialEmptyRectBound); } else { ApplyVisitor apply_visitor(traverse_list_, time_offset); source_->Accept(&apply_visitor); - return apply_visitor.animated(); + results.animated = apply_visitor.animated(); + + // Setup a function for returning + results.get_animation_bounds_since = base::Bind( + &GetAnimationBoundsSince, + scoped_refptr<RefCountedTraversalList>(new RefCountedTraversalList( + apply_visitor.animated_traverse_list())), + time_offset, results.animated); } + return results; } void AnimateNode::CommonInit(const Builder::InternalMap& node_animation_map,
diff --git a/src/cobalt/render_tree/animations/animate_node.h b/src/cobalt/render_tree/animations/animate_node.h index b5a11e1..d0b81c8 100644 --- a/src/cobalt/render_tree/animations/animate_node.h +++ b/src/cobalt/render_tree/animations/animate_node.h
@@ -22,6 +22,7 @@ #include "base/containers/small_map.h" #include "base/memory/ref_counted.h" +#include "cobalt/math/rect_f.h" #include "cobalt/render_tree/animations/animation_list.h" #include "cobalt/render_tree/movable.h" #include "cobalt/render_tree/node.h" @@ -139,10 +140,21 @@ return base::GetTypeId<AnimateNode>(); } + struct AnimateResults { + // The animated render tree, which is guaranteed to not contain any + // AnimateNodes. + scoped_refptr<Node> animated; + + // Can be called in order to return a bounding rectangle around all + // nodes that are actively animated. The parameter specifies a "since" + // time. Any animations that had already expired *before* the "since" time + // will not be included in the returned bounding box. + // This information is likely to be used to enable optimizations where only + // regions of the screen that have changed are rendered. + base::Callback<math::RectF(base::TimeDelta)> get_animation_bounds_since; + }; // Apply the animations to the sub render tree with the given |time_offset|. - // An animated sub-tree is returned, which is guaranteed to not contain any - // AnimateNodes. - scoped_refptr<Node> Apply(base::TimeDelta time_offset); + AnimateResults Apply(base::TimeDelta time_offset); // Returns the sub-tree for which the animations apply to. const scoped_refptr<Node> source() const { return source_; } @@ -154,17 +166,6 @@ const base::TimeDelta& expiry() const { return expiry_; } private: - // A helper render tree visitor class used to compile sub render-tree - // animations. - class TraverseListBuilder; - // A helper render tree visitor class used to apply compiled sub render-tree - // animations. This class follows the traversal generated by - // TraverseListBuilder. - class ApplyVisitor; - - void CommonInit(const Builder::InternalMap& node_animation_map, - const scoped_refptr<Node>& source); - // The compiled node animation list is a sequence of nodes that are either // animated themselves, or on the path to an animated node. Only nodes in // this sequence need to be traversed. TraverseListEntry is an entry in this @@ -180,6 +181,27 @@ }; typedef std::vector<TraverseListEntry> TraverseList; + class RefCountedTraversalList; + + // A helper render tree visitor class used to compile sub render-tree + // animations. + class TraverseListBuilder; + // A helper render tree visitor class used to apply compiled sub render-tree + // animations. This class follows the traversal generated by + // TraverseListBuilder. + class ApplyVisitor; + // A helper class to traverse an animated tree, and compute a bounding + // rectangle for all active animations. + class BoundsVisitor; + + void CommonInit(const Builder::InternalMap& node_animation_map, + const scoped_refptr<Node>& source); + + static math::RectF GetAnimationBoundsSince( + const scoped_refptr<RefCountedTraversalList>& traverse_list, + base::TimeDelta time_offset, const scoped_refptr<Node>& animated, + base::TimeDelta since); + // The compiled traversal list through the sub-tree represented by |source_| // that guides us towards all nodes that need to be animated. TraverseList traverse_list_;
diff --git a/src/cobalt/render_tree/animations/animate_node_test.cc b/src/cobalt/render_tree/animations/animate_node_test.cc index f46e63e..0321bd8 100644 --- a/src/cobalt/render_tree/animations/animate_node_test.cc +++ b/src/cobalt/render_tree/animations/animate_node_test.cc
@@ -48,12 +48,19 @@ template <typename T> scoped_refptr<AnimateNode> CreateSingleAnimation( const scoped_refptr<T>& target, - typename Animation<T>::Function anim_function) { + typename Animation<T>::Function anim_function, base::TimeDelta expiry) { AnimateNode::Builder animations_builder; - animations_builder.Add(target, anim_function); + animations_builder.Add(target, anim_function, expiry); return new AnimateNode(animations_builder, target); } +template <typename T> +scoped_refptr<AnimateNode> CreateSingleAnimation( + const scoped_refptr<T>& target, + typename Animation<T>::Function anim_function) { + return CreateSingleAnimation(target, anim_function, base::TimeDelta::Max()); +} + class ImageFake : public Image { public: const math::Size& GetSize() const OVERRIDE { return size_; } @@ -77,7 +84,7 @@ CreateSingleAnimation(text_node, base::Bind(&AnimateText)); scoped_refptr<Node> animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(1)); + with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; TextNode* animated_text_node = dynamic_cast<TextNode*>(animated_render_tree.get()); EXPECT_TRUE(animated_text_node); @@ -97,7 +104,7 @@ CreateSingleAnimation(image_node, base::Bind(&AnimateImage)); scoped_refptr<Node> animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(1)); + with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; ImageNode* animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); EXPECT_TRUE(animated_image_node); @@ -118,7 +125,7 @@ CreateSingleAnimation(rect_node, base::Bind(&AnimateRect)); scoped_refptr<Node> animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(1)); + with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; RectNode* animated_rect_node = dynamic_cast<RectNode*>(animated_render_tree.get()); EXPECT_TRUE(animated_rect_node); @@ -146,21 +153,21 @@ ImageNode* animated_image_node; animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(1)); + with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); EXPECT_TRUE(animated_image_node); EXPECT_TRUE(animated_image_node); EXPECT_FLOAT_EQ(3.0f, animated_image_node->data().destination_rect.width()); animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(2)); + with_animations->Apply(base::TimeDelta::FromSeconds(2)).animated; animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); EXPECT_TRUE(animated_image_node); EXPECT_TRUE(animated_image_node); EXPECT_FLOAT_EQ(5.0f, animated_image_node->data().destination_rect.width()); animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(4)); + with_animations->Apply(base::TimeDelta::FromSeconds(4)).animated; animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); EXPECT_TRUE(animated_image_node); EXPECT_TRUE(animated_image_node); @@ -198,7 +205,7 @@ new AnimateNode(animations_builder, image_node)); scoped_refptr<Node> animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(3)); + with_animations->Apply(base::TimeDelta::FromSeconds(3)).animated; ImageNode* animated_image_node = dynamic_cast<ImageNode*>(animated_render_tree.get()); @@ -224,7 +231,7 @@ CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform)); scoped_refptr<Node> animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(1)); + with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; MatrixTransformNode* animated_transform_node = dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); @@ -256,7 +263,7 @@ new AnimateNode(animation_builder, composition_node); scoped_refptr<Node> animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(1)); + with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; CompositionNode* animated_composition_node = dynamic_cast<CompositionNode*>(animated_render_tree.get()); @@ -287,7 +294,7 @@ CreateSingleAnimation(transform_node, base::Bind(&AnimateTransform)); scoped_refptr<Node> animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(1)); + with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; MatrixTransformNode* animated_transform_node = dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); @@ -324,7 +331,7 @@ new AnimateNode(transform_node_b); scoped_refptr<Node> animated_render_tree = - with_animations->Apply(base::TimeDelta::FromSeconds(1)); + with_animations->Apply(base::TimeDelta::FromSeconds(1)).animated; MatrixTransformNode* animated_transform_node = dynamic_cast<MatrixTransformNode*>(animated_render_tree.get()); @@ -342,6 +349,159 @@ EXPECT_EQ(RectF(2.0f, 2.0f), animated_image_node->data().destination_rect); } +void BoundsAnimateRect(RectNode::Builder* rect_node, + base::TimeDelta time_elapsed) { + UNREFERENCED_PARAMETER(time_elapsed); + rect_node->rect = RectF(3.0f, 5.0f, 15.0f, 20.0f); +} +TEST(AnimateNodeTest, AnimationBounds) { + scoped_refptr<RectNode> rect_node_static(new RectNode( + RectF(1.0f, 1.0f), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f))))); + + scoped_refptr<RectNode> rect_node_animated(new RectNode( + RectF(4.0f, 4.0f, 10.0f, 10.0f), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f))))); + + CompositionNode::Builder builder; + builder.AddChild(rect_node_static); + builder.AddChild(rect_node_animated); + scoped_refptr<CompositionNode> composition( + new CompositionNode(builder.Pass())); + + AnimateNode::Builder animations_builder; + animations_builder.Add(rect_node_animated, base::Bind(&BoundsAnimateRect)); + scoped_refptr<AnimateNode> with_animations = + new AnimateNode(animations_builder, composition); + + AnimateNode::AnimateResults results = + with_animations->Apply(base::TimeDelta::FromSeconds(1)); + + math::RectF animation_bounds = + results.get_animation_bounds_since.Run(base::TimeDelta()); + + EXPECT_EQ(RectF(3.0f, 5.0f, 15.0f, 20.0f), animation_bounds); +} + +TEST(AnimateNodeTest, AnimationBoundsExpiration) { + scoped_refptr<RectNode> rect_node_static(new RectNode( + RectF(1.0f, 1.0f), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f))))); + + scoped_refptr<RectNode> rect_node_animated(new RectNode( + RectF(4.0f, 4.0f, 10.0f, 10.0f), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f))))); + + CompositionNode::Builder builder; + builder.AddChild(rect_node_static); + builder.AddChild(rect_node_animated); + scoped_refptr<CompositionNode> composition( + new CompositionNode(builder.Pass())); + + AnimateNode::Builder animations_builder; + animations_builder.Add(rect_node_animated, base::Bind(&BoundsAnimateRect), + base::TimeDelta::FromSeconds(2)); + scoped_refptr<AnimateNode> with_animations = + new AnimateNode(animations_builder, composition); + + // Make sure that our animation bounds are updated when we apply animations + // before the expiration. + AnimateNode::AnimateResults results = + with_animations->Apply(base::TimeDelta::FromSeconds(1)); + math::RectF animation_bounds = + results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(0)); + EXPECT_EQ(RectF(3.0f, 5.0f, 15.0f, 20.0f), animation_bounds); + + // Make sure that our animation bounds are updated when we apply animations + // after the expiration, but we pass in a "since" value from before the + // animations expire. + results = with_animations->Apply(base::TimeDelta::FromSeconds(4)); + animation_bounds = + results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(1)); + EXPECT_EQ(RectF(3.0f, 5.0f, 15.0f, 20.0f), animation_bounds); + + // Make sure that our animation bounds are empty after our animations have + // expired. + results = with_animations->Apply(base::TimeDelta::FromSeconds(4)); + animation_bounds = + results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(3)); + EXPECT_EQ(0, animation_bounds.size().GetArea()); +} + +void BoundsAnimateRect2(RectNode::Builder* rect_node, + base::TimeDelta time_elapsed) { + UNREFERENCED_PARAMETER(time_elapsed); + rect_node->rect = RectF(2.0f, 6.0f, 10.0f, 25.0f); +} +TEST(AnimateNodeTest, AnimationBoundsUnionsMultipleAnimations) { + scoped_refptr<RectNode> rect_node_1(new RectNode( + RectF(1.0f, 1.0f), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f))))); + + scoped_refptr<RectNode> rect_node_2(new RectNode( + RectF(4.0f, 4.0f, 10.0f, 10.0f), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f))))); + + CompositionNode::Builder builder; + builder.AddChild(rect_node_1); + builder.AddChild(rect_node_2); + scoped_refptr<CompositionNode> composition( + new CompositionNode(builder.Pass())); + + AnimateNode::Builder animations_builder; + animations_builder.Add(rect_node_1, base::Bind(&BoundsAnimateRect)); + animations_builder.Add(rect_node_2, base::Bind(&BoundsAnimateRect2)); + scoped_refptr<AnimateNode> with_animations = + new AnimateNode(animations_builder, composition); + + // Make sure that our animation bounds are the union from our two animated + // resulting boxes. + AnimateNode::AnimateResults results = + with_animations->Apply(base::TimeDelta::FromSeconds(1)); + math::RectF animation_bounds = + results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(0)); + EXPECT_EQ(RectF(2.0f, 5.0f, 16.0f, 26.0f), animation_bounds); +} + +void AnimateTranslate(CompositionNode::Builder* composition_node, + base::TimeDelta time_elapsed) { + UNREFERENCED_PARAMETER(time_elapsed); + composition_node->set_offset(math::Vector2dF(4.0f, 4.0f)); +} +// This test makes sure that the animation bounds are calculated correctly when +// multiple nodes on the path from the root to a leaf node are animated. Our +// results should use the *animated* path to the node to calculate the bounds, +// versus the non-animated path. +TEST(AnimateNodeTest, AnimationBoundsWorksForCompoundedTransformations) { + scoped_refptr<RectNode> rect_node(new RectNode( + RectF(8.0f, 8.0f), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(1.0f, 1.0f, 1.0f))))); + + CompositionNode::Builder composition_node_builder_1; + composition_node_builder_1.AddChild(rect_node); + scoped_refptr<CompositionNode> composition_node_1( + new CompositionNode(composition_node_builder_1.Pass())); + + CompositionNode::Builder composition_node_builder_2; + composition_node_builder_2.AddChild(composition_node_1); + scoped_refptr<CompositionNode> composition_node_2( + new CompositionNode(composition_node_builder_2.Pass())); + + AnimateNode::Builder animations_builder; + animations_builder.Add(composition_node_1, base::Bind(&AnimateTranslate)); + animations_builder.Add(composition_node_2, base::Bind(&AnimateTranslate)); + scoped_refptr<AnimateNode> with_animations = + new AnimateNode(animations_builder, composition_node_2); + + // Make sure that our animation bounds are the union from our two animated + // resulting boxes. + AnimateNode::AnimateResults results = + with_animations->Apply(base::TimeDelta::FromSeconds(1)); + math::RectF animation_bounds = + results.get_animation_bounds_since.Run(base::TimeDelta::FromSeconds(0)); + EXPECT_EQ(RectF(8.0f, 8.0f, 8.0f, 8.0f), animation_bounds); +} + } // namespace animations } // namespace render_tree } // namespace cobalt
diff --git a/src/cobalt/render_tree/brush.h b/src/cobalt/render_tree/brush.h index d98eeac..b81b95e 100644 --- a/src/cobalt/render_tree/brush.h +++ b/src/cobalt/render_tree/brush.h
@@ -107,6 +107,12 @@ // and destination points. const ColorStopList& color_stops() const { return color_stops_; } + // Returns true if, and only if the brush is horizontal. + bool IsHorizontal() const { return (source_.y() == dest_.y()); } + + // Returns true if, and only if the brush is vertical. + bool IsVertical() const { return (source_.x() == dest_.x()); } + private: math::PointF source_; math::PointF dest_;
diff --git a/src/cobalt/render_tree/color_rgba.h b/src/cobalt/render_tree/color_rgba.h index ee5dbe9..f27f24c 100644 --- a/src/cobalt/render_tree/color_rgba.h +++ b/src/cobalt/render_tree/color_rgba.h
@@ -22,10 +22,21 @@ namespace cobalt { namespace render_tree { +// Note: this does not handle infinities or nans. +inline float clamp(float input, float min, float max) { +#if defined(STARBOARD) + if (SB_UNLIKELY(input < min)) return min; + if (SB_UNLIKELY(input > max)) return max; +#else + if (input < min) return min; + if (input > max) return max; +#endif + return input; +} + // Used to specify a color in the RGB (plus alpha) space. // This color format is referenced by many render_tree objects in order to -// specify a color. It is expressed by a float for each component, and must -// be in the range [0.0, 1.0]. +// specify a color. struct ColorRGBA { public: ColorRGBA() : r_(0), g_(0), b_(0), a_(0) {} @@ -81,13 +92,76 @@ a_ = value; } - float r() const { return r_; } - float g() const { return g_; } - float b() const { return b_; } - float a() const { return a_; } + // These functions clamp the color channel values between 0.0f and 1.0f. + float r() const { return clamp(r_, 0.0f, 1.0f); } + float g() const { return clamp(g_, 0.0f, 1.0f); } + float b() const { return clamp(b_, 0.0f, 1.0f); } + float a() const { return clamp(a_, 0.0f, 1.0f); } + + uint8_t rgb8_r() const { return static_cast<uint8_t>(r() * 255); } + + uint8_t rgb8_g() const { return static_cast<uint8_t>(g() * 255); } + + uint8_t rgb8_b() const { return static_cast<uint8_t>(b() * 255); } + + uint8_t rgb8_a() const { return static_cast<uint8_t>(a() * 255); } + + ColorRGBA& operator=(const ColorRGBA& other) { + r_ = other.r_; + g_ = other.g_; + b_ = other.b_; + a_ = other.a_; + return *this; + } + + ColorRGBA& operator-=(const ColorRGBA& other) { + r_ -= other.r_; + g_ -= other.g_; + b_ -= other.b_; + a_ -= other.a_; + return *this; + } + + ColorRGBA& operator+=(const ColorRGBA& other) { + r_ += other.r_; + g_ += other.g_; + b_ += other.b_; + a_ += other.a_; + return *this; + } + + ColorRGBA& operator+=(const float f) { + r_ += f; + b_ += f; + g_ += f; + a_ += f; + return *this; + } + + ColorRGBA& operator-=(const float f) { + r_ -= f; + b_ -= f; + g_ -= f; + a_ -= f; + return *this; + } + + ColorRGBA& operator*=(const float f) { + r_ *= f; + b_ *= f; + g_ *= f; + a_ *= f; + return *this; + } + + ColorRGBA& operator/=(const float f) { + float one_over_f(1.0f / f); + *this *= one_over_f; + return *this; + } private: - void CheckRange(float value) { + void CheckRange(float value) const { DCHECK_LE(0.0f, value); DCHECK_GE(1.0f, value); } @@ -100,6 +174,46 @@ lhs.a() == rhs.a(); } +inline bool operator!=(const ColorRGBA& lhs, const ColorRGBA& rhs) { + return !(lhs == rhs); +} + +inline ColorRGBA operator-(const ColorRGBA& lhs, const ColorRGBA& rhs) { + ColorRGBA color(lhs); + color -= rhs; + return color; +} + +inline ColorRGBA operator+(const ColorRGBA& lhs, const ColorRGBA& rhs) { + ColorRGBA color(lhs); + color += rhs; + return color; +} + +inline ColorRGBA operator+(const ColorRGBA& lhs, const float rhs) { + ColorRGBA color(lhs); + color += rhs; + return color; +} + +inline ColorRGBA operator-(const ColorRGBA& lhs, const float rhs) { + ColorRGBA color(lhs); + color -= rhs; + return color; +} + +inline ColorRGBA operator*(const ColorRGBA& lhs, const float rhs) { + ColorRGBA color(lhs); + color *= rhs; + return color; +} + +inline ColorRGBA operator/(const ColorRGBA& lhs, const float rhs) { + ColorRGBA color(lhs); + color /= rhs; + return color; +} + // Used by tests. inline std::ostream& operator<<(std::ostream& stream, const ColorRGBA& color) { return stream << "rgba(" << color.r() << ", " << color.g() << ", "
diff --git a/src/cobalt/render_tree/image.h b/src/cobalt/render_tree/image.h index 8faa040..ee58ada 100644 --- a/src/cobalt/render_tree/image.h +++ b/src/cobalt/render_tree/image.h
@@ -67,6 +67,12 @@ // This alpha format implies standard alpha, where each component is // independent of the alpha. kAlphaFormatUnpremultiplied, + + // Indicates that all alpha values in the image data are opaque. If + // non-opaque alpha data is used with this format, visual output will be + // undefined. This information may be used to enable optimizations, and can + // result in Image::IsOpaque() returning true. + kAlphaFormatOpaque, }; // Describes the format of a contiguous block of memory that represents an @@ -216,6 +222,12 @@ BytesPerPixel(kPixelFormatRGBA8)); } + // If an Image is able to know that it contains no alpha data (e.g. if it + // was constructed from ImageData whose alpha format is set to + // kAlphaFormatOpaque), then this method can return true, and code can + // be written to take advantage of this and perform optimizations. + virtual bool IsOpaque() const { return false; } + protected: virtual ~Image() {}
diff --git a/src/cobalt/render_tree/map_to_mesh_filter.h b/src/cobalt/render_tree/map_to_mesh_filter.h index 5451c11..0f996fa 100644 --- a/src/cobalt/render_tree/map_to_mesh_filter.h +++ b/src/cobalt/render_tree/map_to_mesh_filter.h
@@ -17,14 +17,25 @@ #ifndef COBALT_RENDER_TREE_MAP_TO_MESH_FILTER_H_ #define COBALT_RENDER_TREE_MAP_TO_MESH_FILTER_H_ +#include "base/logging.h" +#include "base/memory/ref_counted.h" + namespace cobalt { namespace render_tree { +enum StereoMode { kMono, kLeftRight, kTopBottom }; + // A MapToMeshFilter can be used to map source content onto a 3D mesh, within a // specified well-defined viewport. class MapToMeshFilter { public: - MapToMeshFilter() {} + explicit MapToMeshFilter(const StereoMode stereo_mode) + : stereo_mode_(stereo_mode) {} + + StereoMode GetStereoMode() const { return stereo_mode_; } + + private: + StereoMode stereo_mode_; }; } // namespace render_tree
diff --git a/src/cobalt/render_tree/mesh.h b/src/cobalt/render_tree/mesh.h new file mode 100644 index 0000000..b87793b --- /dev/null +++ b/src/cobalt/render_tree/mesh.h
@@ -0,0 +1,84 @@ +/* + * Copyright 2016 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_RENDER_TREE_MESH_H_ +#define COBALT_RENDER_TREE_MESH_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "starboard/types.h" +#include "third_party/glm/glm/vec2.hpp" +#include "third_party/glm/glm/vec3.hpp" + +namespace cobalt { +namespace render_tree { + +// Represents a mesh to which render_trees can be mapped for 3D projection +// by being applied a filter. +class Mesh { + public: + // Vertices interleave position (x, y, z) and texture (u, v) coordinates. + struct Vertex { + glm::vec3 position_coord; + glm::vec2 texture_coord; + }; + COMPILE_ASSERT(sizeof(Vertex) == sizeof(float) * 5, + vertex_struct_size_exceeds_5_floats); + + // Defines a subset of the legal values for the draw mode parameter passed + // into glDrawArrays() and glDrawElements(). These correspond to GL_TRIANGLES, + // GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN. Users of this class can assert their + // values correspond exactly to the GL constants in order to convert via + // integer casting. + enum DrawMode { + kDrawModeTriangles = 4, + kDrawModeTriangleStrip = 5, + kDrawModeTriangleFan = 6 + }; + + Mesh(const std::vector<Vertex>& vertices, const DrawMode mode) + : vertices_(vertices), draw_mode_(CheckDrawMode(mode)) {} + + ~Mesh() {} + + const std::vector<Vertex>& vertices() const { return vertices_; } + + DrawMode draw_mode() const { return draw_mode_; } + + private: + static const DrawMode CheckDrawMode(DrawMode mode) { + switch (mode) { + case kDrawModeTriangles: + case kDrawModeTriangleStrip: + case kDrawModeTriangleFan: + return mode; + default: + NOTREACHED() << "Unsupported render_tree::Mesh DrawMode detected, " + "defaulting to kDrawModeTriangleStrip"; + return kDrawModeTriangleStrip; + } + } + + const std::vector<Vertex> vertices_; + const DrawMode draw_mode_; +}; + +} // namespace render_tree +} // namespace cobalt + +#endif // COBALT_RENDER_TREE_MESH_H_
diff --git a/src/cobalt/render_tree/mock_resource_provider.h b/src/cobalt/render_tree/mock_resource_provider.h new file mode 100644 index 0000000..f51cba3 --- /dev/null +++ b/src/cobalt/render_tree/mock_resource_provider.h
@@ -0,0 +1,150 @@ +/* + * Copyright 2016 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_RENDER_TREE_MOCK_RESOURCE_PROVIDER_H_ +#define COBALT_RENDER_TREE_MOCK_RESOURCE_PROVIDER_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "cobalt/render_tree/font.h" +#include "cobalt/render_tree/font_provider.h" +#include "cobalt/render_tree/glyph_buffer.h" +#include "cobalt/render_tree/image.h" +#include "cobalt/render_tree/resource_provider.h" +#include "cobalt/render_tree/typeface.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cobalt { +namespace render_tree { + +class MockResourceProvider : public ResourceProvider { + public: + MOCK_METHOD0(Finish, void()); + MOCK_METHOD1(PixelFormatSupported, bool(PixelFormat pixel_format)); + MOCK_METHOD1(AlphaFormatSupported, bool(AlphaFormat alpha_format)); + MOCK_METHOD3(AllocateImageDataMock, + ImageData*(const math::Size& size, PixelFormat pixel_format, + AlphaFormat alpha_format)); + MOCK_METHOD1(CreateImageMock, Image*(ImageData* pixel_data)); + MOCK_METHOD2(AllocateRawImageMemoryMock, + RawImageMemory*(size_t size_in_bytes, size_t alignment)); + MOCK_METHOD2(CreateMultiPlaneImageFromRawMemoryMock, + Image*(RawImageMemory* raw_image_memory, + const MultiPlaneImageDataDescriptor& descriptor)); + MOCK_CONST_METHOD1(HasLocalFontFamily, bool(const char* font_family_name)); + MOCK_METHOD2(GetLocalTypefaceMock, + Typeface*(const char* font_family_name, FontStyle font_style)); + MOCK_METHOD1(GetLocalTypefaceIfAvailableMock, + Typeface*(const std::string& font_face_name)); + MOCK_METHOD3(GetCharacterFallbackTypefaceMock, + Typeface*(int32 utf32_character, FontStyle font_style, + const std::string& language)); + MOCK_METHOD2(CreateTypefaceFromRawDataMock, + Typeface*(RawTypefaceDataVector* raw_data, + std::string* error_string)); + MOCK_METHOD5( + CreateGlyphBufferMock, + render_tree::GlyphBuffer*(const char16* text_buffer, size_t text_length, + const std::string& language, bool is_rtl, + render_tree::FontProvider* font_provider)); + MOCK_METHOD2( + CreateGlyphBufferMock, + render_tree::GlyphBuffer*(const std::string& utf8_string, + const scoped_refptr<render_tree::Font>& font)); + MOCK_METHOD6(GetTextWidth, + float(const char16* text_buffer, size_t text_length, + const std::string& language, bool is_rtl, + render_tree::FontProvider* font_provider, + render_tree::FontVector* maybe_used_fonts)); + + scoped_ptr<ImageData> AllocateImageData(const math::Size& size, + PixelFormat pixel_format, + AlphaFormat alpha_format) { + return scoped_ptr<ImageData>( + AllocateImageDataMock(size, pixel_format, alpha_format)); + } + scoped_refptr<Image> CreateImage(scoped_ptr<ImageData> pixel_data) { + return scoped_refptr<Image>(CreateImageMock(pixel_data.get())); + } + +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + scoped_refptr<Image> CreateImageFromSbDecodeTarget(SbDecodeTarget target) { + UNREFERENCED_PARAMETER(target); + return NULL; + } + + SbDecodeTargetProvider* GetSbDecodeTargetProvider() { return NULL; } + + bool SupportsSbDecodeTarget() { return false; } +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) +#endif // defined(STARBOARD) + + scoped_ptr<RawImageMemory> AllocateRawImageMemory(size_t size_in_bytes, + size_t alignment) { + return scoped_ptr<RawImageMemory>( + AllocateRawImageMemoryMock(size_in_bytes, alignment)); + } + scoped_refptr<Image> CreateMultiPlaneImageFromRawMemory( + scoped_ptr<RawImageMemory> raw_image_memory, + const MultiPlaneImageDataDescriptor& descriptor) { + return scoped_refptr<Image>(CreateMultiPlaneImageFromRawMemoryMock( + raw_image_memory.get(), descriptor)); + } + scoped_refptr<Typeface> GetLocalTypeface(const char* font_family_name, + FontStyle font_style) { + return scoped_refptr<Typeface>( + GetLocalTypefaceMock(font_family_name, font_style)); + } + scoped_refptr<Typeface> GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) { + return scoped_refptr<Typeface>( + GetLocalTypefaceIfAvailableMock(font_face_name)); + } + scoped_refptr<Typeface> GetCharacterFallbackTypeface( + int32 utf32_character, FontStyle font_style, + const std::string& language) { + return scoped_refptr<Typeface>(GetCharacterFallbackTypefaceMock( + utf32_character, font_style, language)); + } + scoped_refptr<Typeface> CreateTypefaceFromRawData( + scoped_ptr<RawTypefaceDataVector> raw_data, std::string* error_string) { + return scoped_refptr<Typeface>( + CreateTypefaceFromRawDataMock(raw_data.get(), error_string)); + } + scoped_refptr<render_tree::GlyphBuffer> CreateGlyphBuffer( + const char16* text_buffer, size_t text_length, + const std::string& language, bool is_rtl, + render_tree::FontProvider* font_provider) { + return scoped_refptr<render_tree::GlyphBuffer>(CreateGlyphBufferMock( + text_buffer, text_length, language, is_rtl, font_provider)); + } + scoped_refptr<render_tree::GlyphBuffer> CreateGlyphBuffer( + const std::string& utf8_string, + const scoped_refptr<render_tree::Font>& font) { + return scoped_refptr<render_tree::GlyphBuffer>( + CreateGlyphBufferMock(utf8_string, font.get())); + } +}; + +} // namespace render_tree +} // namespace cobalt + +#endif // COBALT_RENDER_TREE_MOCK_RESOURCE_PROVIDER_H_
diff --git a/src/cobalt/render_tree/render_tree.gyp b/src/cobalt/render_tree/render_tree.gyp index 5d6f1a6..d8dfa5e 100644 --- a/src/cobalt/render_tree/render_tree.gyp +++ b/src/cobalt/render_tree/render_tree.gyp
@@ -47,6 +47,7 @@ 'map_to_mesh_filter.h', 'matrix_transform_node.cc', 'matrix_transform_node.h', + 'mesh.h', 'node.h', 'node_visitor.h', 'opacity_filter.h',
diff --git a/src/cobalt/render_tree/resource_provider.h b/src/cobalt/render_tree/resource_provider.h index 72c106a..4590121 100644 --- a/src/cobalt/render_tree/resource_provider.h +++ b/src/cobalt/render_tree/resource_provider.h
@@ -27,6 +27,9 @@ #include "cobalt/render_tree/glyph_buffer.h" #include "cobalt/render_tree/image.h" #include "cobalt/render_tree/typeface.h" +#if defined(STARBOARD) +#include "starboard/decode_target.h" +#endif // defined(STARBOARD) namespace cobalt { namespace render_tree { @@ -72,6 +75,23 @@ virtual scoped_refptr<Image> CreateImage( scoped_ptr<ImageData> pixel_data) = 0; +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + // This function will consume an SbDecodeTarget object produced by + // SbDecodeTargetCreate(), wrap it in a render_tree::Image that can be used + // in a render tree, and return it to the caller. + virtual scoped_refptr<Image> CreateImageFromSbDecodeTarget( + SbDecodeTarget target) = 0; + + // Return the associated SbDecodeTargetProvider with the ResourceProvider, + // if it exists. Returns NULL if SbDecodeTarget is not supported. + virtual SbDecodeTargetProvider* GetSbDecodeTargetProvider() = 0; + + // Whether SbDecodeTargetIsSupported or not. + virtual bool SupportsSbDecodeTarget() = 0; +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) +#endif // defined(STARBOARD) + // Returns a raw chunk of memory that can later be passed into a function like // CreateMultiPlaneImageFromRawMemory() in order to create a texture. // If possible, the memory returned will be GPU memory that can be directly @@ -107,6 +127,16 @@ virtual scoped_refptr<Typeface> GetLocalTypeface(const char* font_family_name, FontStyle font_style) = 0; + // Given a set of typeface information, this method returns the locally + // available typeface that best fits the specified parameters. In the case + // where no typeface is found that matches the font family name, NULL is + // returned. + // + // Font's typeface (aka face name) is combination of a style and a font + // family. Font's style consists of weight, and a slant (but not size). + virtual scoped_refptr<Typeface> GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) = 0; + // Given a UTF-32 character, a set of typeface information, and a language, // this method returns the best-fit locally available fallback typeface that // provides a glyph for the specified character. In the case where no fallback
diff --git a/src/cobalt/render_tree/resource_provider_stub.h b/src/cobalt/render_tree/resource_provider_stub.h index 1430f5f..d40787f 100644 --- a/src/cobalt/render_tree/resource_provider_stub.h +++ b/src/cobalt/render_tree/resource_provider_stub.h
@@ -187,6 +187,21 @@ return make_scoped_refptr(new ImageStub(skia_source_data.Pass())); } +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + scoped_refptr<Image> CreateImageFromSbDecodeTarget( + SbDecodeTarget decode_target) OVERRIDE { + NOTREACHED(); + SbDecodeTargetDestroy(decode_target); + return NULL; + } + + SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; } + + bool SupportsSbDecodeTarget() OVERRIDE { return false; } +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) +#endif // defined(STARBOARD) + scoped_ptr<RawImageMemory> AllocateRawImageMemory(size_t size_in_bytes, size_t alignment) OVERRIDE { return scoped_ptr<RawImageMemory>( @@ -213,6 +228,12 @@ return make_scoped_refptr(new TypefaceStub(NULL)); } + scoped_refptr<render_tree::Typeface> GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) OVERRIDE { + UNREFERENCED_PARAMETER(font_face_name); + return make_scoped_refptr(new TypefaceStub(NULL)); + } + scoped_refptr<Typeface> GetCharacterFallbackTypeface( int32 utf32_character, FontStyle font_style, const std::string& language) OVERRIDE {
diff --git a/src/cobalt/renderer/animations_test.cc b/src/cobalt/renderer/animations_test.cc index 033a78f..b46c59e 100644 --- a/src/cobalt/renderer/animations_test.cc +++ b/src/cobalt/renderer/animations_test.cc
@@ -1,4 +1,4 @@ -/* + /* * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -171,7 +171,7 @@ Pipeline pipeline( base::Bind(render_module_options.create_rasterizer_function, graphics_context.get(), render_module_options), - dummy_output_surface, NULL); + dummy_output_surface, NULL, true); // Our test render tree will consist of only a single ImageNode. scoped_refptr<ImageNode> test_node = new ImageNode(
diff --git a/src/cobalt/renderer/backend/blitter/graphics_context.cc b/src/cobalt/renderer/backend/blitter/graphics_context.cc index b6e4210..3e9613f 100644 --- a/src/cobalt/renderer/backend/blitter/graphics_context.cc +++ b/src/cobalt/renderer/backend/blitter/graphics_context.cc
@@ -75,6 +75,12 @@ return pixels.Pass(); } +void GraphicsContextBlitter::Finish() { + // Note: flushing the context doesn't actually guarantee that drawing has + // finished. + SbBlitterFlushContext(context_); +} + } // namespace backend } // namespace renderer } // namespace cobalt
diff --git a/src/cobalt/renderer/backend/blitter/graphics_context.h b/src/cobalt/renderer/backend/blitter/graphics_context.h index 5c15184..5fc3330 100644 --- a/src/cobalt/renderer/backend/blitter/graphics_context.h +++ b/src/cobalt/renderer/backend/blitter/graphics_context.h
@@ -38,6 +38,7 @@ const math::Size& dimensions) OVERRIDE; scoped_array<uint8_t> DownloadPixelDataAsRGBA( const scoped_refptr<RenderTarget>& render_target) OVERRIDE; + void Finish() OVERRIDE; SbBlitterContext GetSbBlitterContext() const { return context_; } SbBlitterDevice GetSbBlitterDevice() const { return device_; }
diff --git a/src/cobalt/renderer/backend/egl/display.cc b/src/cobalt/renderer/backend/egl/display.cc index dc325c7..c294168 100644 --- a/src/cobalt/renderer/backend/egl/display.cc +++ b/src/cobalt/renderer/backend/egl/display.cc
@@ -54,6 +54,16 @@ surface_ = eglCreateWindowSurface(display_, config_, native_window_, NULL); CHECK_EQ(EGL_SUCCESS, eglGetError()); + // Configure the surface to preserve contents on swap. + EGLBoolean surface_attrib_set = + eglSurfaceAttrib(display_, surface_, + EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); + // NOTE: Must check eglGetError() to clear any error flags and also check + // the return value of eglSurfaceAttrib since some implementations may not + // set the error condition. + content_preserved_on_swap_ = + eglGetError() == EGL_SUCCESS && surface_attrib_set == EGL_TRUE; + // Query and cache information about the surface now that we have created it. EGLint egl_surface_width; EGLint egl_surface_height;
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.cc b/src/cobalt/renderer/backend/egl/graphics_context.cc index 3482472..2f39ddb 100644 --- a/src/cobalt/renderer/backend/egl/graphics_context.cc +++ b/src/cobalt/renderer/backend/egl/graphics_context.cc
@@ -211,6 +211,7 @@ GraphicsContextEGL::~GraphicsContextEGL() { MakeCurrent(); + GL_CALL(glFinish()); GL_CALL(glDeleteBuffers(1, &blit_vertex_buffer_)); GL_CALL(glDeleteProgram(blit_program_)); GL_CALL(glDeleteShader(blit_fragment_shader_)); @@ -350,6 +351,11 @@ return pixels.Pass(); } +void GraphicsContextEGL::Finish() { + ScopedMakeCurrent scoped_current_context(this); + GL_CALL(glFinish()); +} + void GraphicsContextEGL::Blit(GLuint texture, int x, int y, int width, int height) { // Render a texture to the specified output rectangle on the render target.
diff --git a/src/cobalt/renderer/backend/egl/graphics_context.h b/src/cobalt/renderer/backend/egl/graphics_context.h index 57d77aa..c69ed50 100644 --- a/src/cobalt/renderer/backend/egl/graphics_context.h +++ b/src/cobalt/renderer/backend/egl/graphics_context.h
@@ -58,6 +58,8 @@ scoped_array<uint8_t> DownloadPixelDataAsRGBA( const scoped_refptr<RenderTarget>& render_target) OVERRIDE; + void Finish() OVERRIDE; + // Helper class to allow one to create a RAII object that will acquire the // current context upon construction and release it upon destruction. class ScopedMakeCurrent { @@ -106,6 +108,13 @@ void Blit(GLuint texture, int x, int y, int width, int height); + bool ReadPixelsNeedVerticalFlip() { + if (!read_pixels_needs_vertical_flip_) { + read_pixels_needs_vertical_flip_ = ComputeReadPixelsNeedVerticalFlip(); + } + return *read_pixels_needs_vertical_flip_; + } + private: // Performs a test to determine if the pixel data returned by glReadPixels // needs to be vertically flipped or not. This test is expensive and so
diff --git a/src/cobalt/renderer/backend/egl/graphics_system.cc b/src/cobalt/renderer/backend/egl/graphics_system.cc index 16b5104..d8895bd 100644 --- a/src/cobalt/renderer/backend/egl/graphics_system.cc +++ b/src/cobalt/renderer/backend/egl/graphics_system.cc
@@ -64,23 +64,35 @@ // Setup our configuration to support RGBA and compatibility with PBuffer // objects (for offscreen rendering). - EGLint const attribute_list[] = {EGL_RED_SIZE, - 8, - EGL_GREEN_SIZE, - 8, - EGL_BLUE_SIZE, - 8, - EGL_ALPHA_SIZE, - 8, - EGL_SURFACE_TYPE, - EGL_WINDOW_BIT | EGL_PBUFFER_BIT, - EGL_BIND_TO_TEXTURE_RGBA, - EGL_TRUE, - EGL_NONE}; + EGLint attribute_list[] = {EGL_SURFACE_TYPE, // this must be first + EGL_WINDOW_BIT | EGL_PBUFFER_BIT | + EGL_SWAP_BEHAVIOR_PRESERVED_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_BIND_TO_TEXTURE_RGBA, + EGL_TRUE, + EGL_NONE}; + + // Try to allow preservation of the frame contents between swap calls -- + // this will allow rendering of only parts of the frame that have changed. + DCHECK_EQ(EGL_SURFACE_TYPE, attribute_list[0]); + EGLint& surface_type_value = attribute_list[1]; EGLint num_configs; - EGL_CALL( - eglChooseConfig(display_, attribute_list, &config_, 1, &num_configs)); + eglChooseConfig(display_, attribute_list, &config_, 1, &num_configs); + if (eglGetError() != EGL_SUCCESS || num_configs == 0) { + // Swap buffer preservation may not be supported. Try to find a config + // without the feature. + surface_type_value &= ~EGL_SWAP_BEHAVIOR_PRESERVED_BIT; + EGL_CALL( + eglChooseConfig(display_, attribute_list, &config_, 1, &num_configs)); + } DCHECK_EQ(1, num_configs); #if defined(GLES3_SUPPORTED)
diff --git a/src/cobalt/renderer/backend/egl/render_target.h b/src/cobalt/renderer/backend/egl/render_target.h index d88c385..9256372 100644 --- a/src/cobalt/renderer/backend/egl/render_target.h +++ b/src/cobalt/renderer/backend/egl/render_target.h
@@ -27,7 +27,11 @@ class RenderTargetEGL : public RenderTarget { public: - RenderTargetEGL() : swap_count_(0), has_been_made_current_(false) {} + RenderTargetEGL() + : swap_count_(0) + , has_been_made_current_(false) + , content_preserved_on_swap_(false) + {} // An EGLSurface is needed for the EGL function eglMakeCurrent() which // associates a render target with a rendering context. @@ -40,6 +44,10 @@ virtual bool IsWindowRenderTarget() const { return false; } + // Returns whether the render target contents are preserved after the + // target has been displayed via eglSwapBuffers(). + bool IsContentPreservedOnSwap() const { return content_preserved_on_swap_; } + int64 swap_count() { return swap_count_; } void increment_swap_count() { ++swap_count_; } @@ -51,6 +59,7 @@ int64 swap_count_; bool has_been_made_current_; + bool content_preserved_on_swap_; }; } // namespace backend
diff --git a/src/cobalt/renderer/backend/egl/texture_data_pbo.cc b/src/cobalt/renderer/backend/egl/texture_data_pbo.cc index edc0e40..86fc108 100644 --- a/src/cobalt/renderer/backend/egl/texture_data_pbo.cc +++ b/src/cobalt/renderer/backend/egl/texture_data_pbo.cc
@@ -32,7 +32,7 @@ TextureDataPBO::TextureDataPBO(ResourceContext* resource_context, const math::Size& size, GLenum format) : resource_context_(resource_context), size_(size), format_(format) { - data_size_ = GetPitchInBytes() * size_.height(); + data_size_ = static_cast<int64>(GetPitchInBytes()) * size_.height(); resource_context_->RunSynchronouslyWithinResourceContext( base::Bind(&TextureDataPBO::InitAndMapPBO, base::Unretained(this))); @@ -57,7 +57,7 @@ GL_PIXEL_UNPACK_BUFFER, 0, data_size_, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT)); - DCHECK(mapped_data_); + CHECK(mapped_data_); DCHECK_EQ(GL_NO_ERROR, glGetError()); GL_CALL(glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0)); }
diff --git a/src/cobalt/renderer/backend/egl/texture_data_pbo.h b/src/cobalt/renderer/backend/egl/texture_data_pbo.h index 56754ea..d99ca54 100644 --- a/src/cobalt/renderer/backend/egl/texture_data_pbo.h +++ b/src/cobalt/renderer/backend/egl/texture_data_pbo.h
@@ -63,7 +63,7 @@ math::Size size_; GLenum format_; GLuint pixel_buffer_; - int data_size_; + int64 data_size_; GLubyte* mapped_data_; };
diff --git a/src/cobalt/renderer/backend/graphics_context.h b/src/cobalt/renderer/backend/graphics_context.h index e5743f5..bd51d53 100644 --- a/src/cobalt/renderer/backend/graphics_context.h +++ b/src/cobalt/renderer/backend/graphics_context.h
@@ -62,6 +62,9 @@ virtual scoped_array<uint8_t> DownloadPixelDataAsRGBA( const scoped_refptr<RenderTarget>& render_target) = 0; + // Waits until all drawing is finished. + virtual void Finish() = 0; + private: GraphicsSystem* system_; };
diff --git a/src/cobalt/renderer/backend/graphics_context_stub.h b/src/cobalt/renderer/backend/graphics_context_stub.h index 5cb36bd..b2e0f1b 100644 --- a/src/cobalt/renderer/backend/graphics_context_stub.h +++ b/src/cobalt/renderer/backend/graphics_context_stub.h
@@ -45,6 +45,8 @@ return scoped_array<uint8_t>( new uint8_t[render_target->GetSize().GetArea() * 4]); } + + void Finish() OVERRIDE {} }; } // namespace backend
diff --git a/src/cobalt/renderer/frame_rate_throttler.cc b/src/cobalt/renderer/frame_rate_throttler.cc new file mode 100644 index 0000000..20d662e --- /dev/null +++ b/src/cobalt/renderer/frame_rate_throttler.cc
@@ -0,0 +1,47 @@ +/* + * Copyright 2016 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/renderer/frame_rate_throttler.h" + +#include "base/threading/platform_thread.h" + +namespace cobalt { +namespace renderer { + +void FrameRateThrottler::BeginInterval() { + if (COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS > 0) { + begin_time_ = base::TimeTicks::HighResNow(); + } +} + +void FrameRateThrottler::EndInterval() { + // Throttle presentation of new frames if a minimum frame time is specified. + if (COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS > 0) { + if (!begin_time_.is_null()) { + base::TimeDelta elapsed = base::TimeTicks::HighResNow() - begin_time_; + base::TimeDelta wait_time = + base::TimeDelta::FromMillisecondsD( + COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS) - + elapsed; + if (wait_time > base::TimeDelta::FromMicroseconds(0)) { + base::PlatformThread::Sleep(wait_time); + } + } + } +} + +} // namespace renderer +} // namespace cobalt
diff --git a/src/cobalt/renderer/frame_rate_throttler.h b/src/cobalt/renderer/frame_rate_throttler.h new file mode 100644 index 0000000..6af2be7 --- /dev/null +++ b/src/cobalt/renderer/frame_rate_throttler.h
@@ -0,0 +1,44 @@ +/* + * Copyright 2016 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_RENDERER_FRAME_RATE_THROTTLER_H_ +#define COBALT_RENDERER_FRAME_RATE_THROTTLER_H_ + +#include "base/time.h" + +namespace cobalt { +namespace renderer { + +// The FrameRateThrottler is used to enforce a minimum frame time. The +// rasterizer should call the provided hooks just before and after submitting +// a new frame. This can be used to throttle the frame rate (to 30 Hz for +// example) when the presentation interval cannot be specified. +class FrameRateThrottler { + public: + // To be called just after submitting a new frame. + void BeginInterval(); + + // To be called just before submitting a new frame. + void EndInterval(); + + private: + base::TimeTicks begin_time_; +}; + +} // namespace renderer +} // namespace cobalt + +#endif // COBALT_RENDERER_FRAME_RATE_THROTTLER_H_
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_nv12.glsl b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_nv12.glsl index 6fcae22..0a0d63b 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_nv12.glsl +++ b/src/cobalt/renderer/glimp_shaders/glsl/fragment_skia_nv12.glsl
@@ -11,7 +11,7 @@ // Stage 0: YUV to RGB output_Stage0 = vec4( texture2D(uSampler0_Stage0, vMatrixCoord_Stage0).aaaa.r, - texture2D(uSampler1_Stage0, vMatrixCoord_Stage0).ba, + texture2D(uSampler1_Stage0, vMatrixCoord_Stage0).ra, 1.0) * uYUVMatrix_Stage0; } gl_FragColor = output_Stage0;
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi b/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi index b0e8820..7500fc3 100644 --- a/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi +++ b/src/cobalt/renderer/glimp_shaders/glsl/shaders.gypi
@@ -64,6 +64,7 @@ 'vertex_skia_antialiased_color_only.glsl', 'vertex_skia_antialiased_oval.glsl', 'vertex_skia_color_only.glsl', + 'vertex_mesh.glsl', 'vertex_skia_texcoords_and_color.glsl', 'vertex_skia_texcoords_and_color_with_texcoord_matrix.glsl', 'vertex_skia_texcoords_derived_from_position.glsl',
diff --git a/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl b/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl new file mode 100644 index 0000000..b41b78f --- /dev/null +++ b/src/cobalt/renderer/glimp_shaders/glsl/vertex_mesh.glsl
@@ -0,0 +1,11 @@ +attribute vec3 a_position; +attribute vec2 a_tex_coord; +varying vec2 v_tex_coord; +uniform vec2 u_tex_offset; +uniform vec2 u_tex_multiplier; +uniform mat4 u_mvp_matrix; +void main() { + gl_Position = u_mvp_matrix * vec4(a_position.xyz, 1.0); + v_tex_coord.x = a_tex_coord.x * u_tex_multiplier.x + u_tex_offset.x; + v_tex_coord.y = a_tex_coord.y * u_tex_multiplier.y + u_tex_offset.y; +}
diff --git a/src/cobalt/renderer/pipeline.cc b/src/cobalt/renderer/pipeline.cc index df77418..58d850b 100644 --- a/src/cobalt/renderer/pipeline.cc +++ b/src/cobalt/renderer/pipeline.cc
@@ -65,15 +65,15 @@ Pipeline::Pipeline(const CreateRasterizerFunction& create_rasterizer_function, const scoped_refptr<backend::RenderTarget>& render_target, - backend::GraphicsContext* graphics_context) + backend::GraphicsContext* graphics_context, + bool submit_even_if_render_tree_is_unchanged) : rasterizer_created_event_(true, false), render_target_(render_target), graphics_context_(graphics_context), rasterizer_thread_("Rasterizer"), submission_disposal_thread_("Rasterizer Submission Disposal"), - rasterize_current_tree_interval_timer_( - "Renderer.Rasterize.Interval", - kRasterizeCurrentTreeTimerTimeIntervalInMs), + submit_even_if_render_tree_is_unchanged_( + submit_even_if_render_tree_is_unchanged), rasterize_current_tree_timer_("Renderer.Rasterize.Duration", kRasterizeCurrentTreeTimerTimeIntervalInMs) #if defined(ENABLE_DEBUG_CONSOLE) @@ -121,6 +121,12 @@ base::Unretained(&submission_queue_shutdown))); submission_queue_shutdown.Wait(); + // This potential reference to a render tree whose animations may have ended + // must be destroyed before we shutdown the rasterizer thread since it may + // contain references to render tree nodes and resources. + last_rendered_expired_render_tree_ = NULL; + last_render_tree_ = NULL; + // Submit a shutdown task to the rasterizer thread so that it can shutdown // anything that must be shutdown from that thread. rasterizer_thread_.message_loop()->PostTask( @@ -221,17 +227,15 @@ base::TimeTicks now = base::TimeTicks::Now(); Submission submission = submission_queue_->GetCurrentSubmission(now); -#if defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE) // If our render tree hasn't changed from the one that was previously rendered // and the animations on the previously rendered tree have expired, and it's // okay on this system to not flip the display buffer frequently, then we // can just not do anything here. - if (submission.render_tree == last_rendered_expired_render_tree_) { + if (!submit_even_if_render_tree_is_unchanged_ && + submission.render_tree == last_rendered_expired_render_tree_) { return; } -#endif // #if defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE) - rasterize_current_tree_interval_timer_.Start(now); rasterize_current_tree_timer_.Start(now); // Rasterize the last submitted render tree. @@ -239,17 +243,18 @@ rasterize_current_tree_timer_.Stop(); -#if defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE) // Check whether the animations in the render tree that was just rasterized // have expired or not, and if so, mark that down so that if we see it in // the future we don't spend the time re-rendering it. - render_tree::animations::AnimateNode* animate_node = - base::polymorphic_downcast<render_tree::animations::AnimateNode*>( - submission.render_tree.get()); - last_rendered_expired_render_tree_ = - animate_node->expiry() <= submission.time_offset ? submission.render_tree - : NULL; -#endif // defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE) + if (!submit_even_if_render_tree_is_unchanged_) { + render_tree::animations::AnimateNode* animate_node = + base::polymorphic_downcast<render_tree::animations::AnimateNode*>( + submission.render_tree.get()); + last_rendered_expired_render_tree_ = + animate_node->expiry() <= submission.time_offset + ? submission.render_tree + : NULL; + } } void Pipeline::RasterizeSubmissionToRenderTarget( @@ -257,19 +262,45 @@ const scoped_refptr<backend::RenderTarget>& render_target) { TRACE_EVENT0("cobalt::renderer", "Pipeline::RasterizeSubmissionToRenderTarget()"); + + // Keep track of the last render tree that we rendered so that we can watch + // if it changes, in which case we should reset our tracked + // |previous_animated_area_|. + if (submission.render_tree != last_render_tree_) { + last_render_tree_ = submission.render_tree; + previous_animated_area_ = base::nullopt; + last_render_time_ = base::nullopt; + } + // Animate the render tree using the submitted animations. render_tree::animations::AnimateNode* animate_node = base::polymorphic_downcast<render_tree::animations::AnimateNode*>( submission.render_tree.get()); - scoped_refptr<Node> animated_render_tree = + render_tree::animations::AnimateNode::AnimateResults results = animate_node->Apply(submission.time_offset); + // Calculate a bounding box around the active animations. Union it with the + // bounding box around active animations from the previous frame, and we get + // a scissor rectangle marking the dirty regions of the screen. + math::RectF animated_bounds = results.get_animation_bounds_since.Run( + last_render_time_ ? *last_render_time_ : base::TimeDelta()); + math::Rect rounded_bounds = math::RoundOut(animated_bounds); + base::optional<math::Rect> redraw_area; + if (previous_animated_area_) { + redraw_area = math::UnionRects(rounded_bounds, *previous_animated_area_); + } + previous_animated_area_ = rounded_bounds; + // Rasterize the animated render tree. - rasterizer_->Submit(animated_render_tree, render_target); + rasterizer::Rasterizer::Options rasterizer_options; + rasterizer_options.dirty = redraw_area; + rasterizer_->Submit(results.animated, render_target, rasterizer_options); if (!submission.on_rasterized_callback.is_null()) { submission.on_rasterized_callback.Run(); } + + last_render_time_ = submission.time_offset; } void Pipeline::InitializeRasterizerThread( @@ -344,11 +375,10 @@ render_tree::animations::AnimateNode* animate_node = base::polymorphic_downcast<render_tree::animations::AnimateNode*>( submission.render_tree.get()); - scoped_refptr<Node> animated_render_tree = + render_tree::animations::AnimateNode::AnimateResults results = animate_node->Apply(submission.time_offset); - std::string tree_dump = - render_tree::DumpRenderTreeToString(animated_render_tree); + std::string tree_dump = render_tree::DumpRenderTreeToString(results.animated); if (message.empty() || message == "undefined") { // If no filename was specified, send output to the console. LOG(INFO) << tree_dump.c_str();
diff --git a/src/cobalt/renderer/pipeline.h b/src/cobalt/renderer/pipeline.h index cec3874..d3a2e96 100644 --- a/src/cobalt/renderer/pipeline.h +++ b/src/cobalt/renderer/pipeline.h
@@ -34,13 +34,6 @@ #include "cobalt/renderer/submission.h" #include "cobalt/renderer/submission_queue.h" -#if defined(OS_STARBOARD) && !SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER -// If there is no need to frequently flip the display buffer, then enable -// support for an optimization where the scene is not re-rasterized each frame -// if it has not changed from the last frame. -#define DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE -#endif - namespace cobalt { namespace renderer { @@ -66,7 +59,8 @@ // thread safe objects. Pipeline(const CreateRasterizerFunction& create_rasterizer_function, const scoped_refptr<backend::RenderTarget>& render_target, - backend::GraphicsContext* graphics_context); + backend::GraphicsContext* graphics_context, + bool submit_even_if_render_tree_is_unchanged); ~Pipeline(); // Submit a new render tree to the renderer pipeline. After calling this @@ -173,17 +167,26 @@ // the future. base::optional<SubmissionQueue> submission_queue_; -#if defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE) // Keep track of the last render tree we rendered whose animations have // expired so that if we see that we are asked to rasterize that render tree // again, we know that we do not have to do anything. scoped_refptr<render_tree::Node> last_rendered_expired_render_tree_; -#endif // defined(DO_NOT_FLIP_BUFFERS_IF_RENDER_TREE_DOES_NOT_CHANGE) - // Timers for tracking how frequently |RasterizeCurrentTree| is called and - // the amount of time spent in |RasterizeCurrentTree| each call. - base::CValTimeIntervalTimer<base::CValPublic> - rasterize_current_tree_interval_timer_; + // If true, we will submit the current render tree to the rasterizer every + // frame, even if it hasn't changed. + const bool submit_even_if_render_tree_is_unchanged_; + + // Keeps track of the last rendered animated render tree. + scoped_refptr<render_tree::Node> last_render_tree_; + // Keeps track of the area of the screen that animations previously existed + // within, so that we can know which regions of the screens would be dirty + // next frame. + base::optional<math::Rect> previous_animated_area_; + // The submission time used during the last render tree render. + base::optional<base::TimeDelta> last_render_time_; + + // Timer tracking the amount of time spent in |RasterizeCurrentTree| each + // call. base::CValTimeIntervalTimer<base::CValPublic> rasterize_current_tree_timer_; #if defined(ENABLE_DEBUG_CONSOLE)
diff --git a/src/cobalt/renderer/pipeline_test.cc b/src/cobalt/renderer/pipeline_test.cc index f66b4e5..c70d44e 100644 --- a/src/cobalt/renderer/pipeline_test.cc +++ b/src/cobalt/renderer/pipeline_test.cc
@@ -52,7 +52,7 @@ void Submit(const scoped_refptr<cobalt::render_tree::Node>&, const scoped_refptr<cobalt::renderer::backend::RenderTarget>&, - int options) OVERRIDE { + const Options& options) OVERRIDE { if (last_submission_time) { // Simulate a "wait for vsync". base::TimeDelta since_last_submit = @@ -88,7 +88,8 @@ submission_count_ = 0; start_time_ = base::TimeTicks::Now(); pipeline_.reset(new Pipeline( - base::Bind(&CreateMockRasterizer, &submission_count_), NULL, NULL)); + base::Bind(&CreateMockRasterizer, &submission_count_), NULL, NULL, + true)); } static void DummyAnimateFunction(
diff --git a/src/cobalt/renderer/rasterizer/benchmark.cc b/src/cobalt/renderer/rasterizer/benchmark.cc index 097d0bf..c0d2567 100644 --- a/src/cobalt/renderer/rasterizer/benchmark.cc +++ b/src/cobalt/renderer/rasterizer/benchmark.cc
@@ -116,11 +116,14 @@ for (int i = 0; i < kRenderIterationCount; ++i) { AnimateNode* animate_node = base::polymorphic_downcast<AnimateNode*>(scene.get()); - scoped_refptr<Node> animated = animate_node->Apply( - base::TimeDelta::FromSecondsD(i * kFixedTimeStepInSecondsPerFrame)); + scoped_refptr<Node> animated = + animate_node->Apply(base::TimeDelta::FromSecondsD( + i * kFixedTimeStepInSecondsPerFrame)) + .animated; // Submit the render tree to be rendered. rasterizer->Submit(animated, test_surface); + graphics_context->Finish(); if (i == 0) { // Enable tracing again after one iteration has passed and any lazy @@ -183,6 +186,10 @@ CreateDefaultRasterizer(graphics_context.get()); ResourceProvider* resource_provider = rasterizer->GetResourceProvider(); + if (!resource_provider->AlphaFormatSupported(alpha_format)) { + // Only run the test if the alpha format is supported. + return; + } const int kIterationCount = 20; const Size kImageSize(400, 400);
diff --git a/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.cc b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.cc new file mode 100644 index 0000000..5e65c9c --- /dev/null +++ b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.cc
@@ -0,0 +1,251 @@ +/* + * Copyright 2016 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/renderer/rasterizer/blitter/cached_software_rasterizer.h" + +#include <utility> + +#include "cobalt/math/vector2d_f.h" +#include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkImageInfo.h" + +#if SB_HAS(BLITTER) + +namespace cobalt { +namespace renderer { +namespace rasterizer { +namespace blitter { + +size_t CachedSoftwareRasterizer::Surface::GetEstimatedMemoryUsage() const { + // We assume 4-bytes-per-pixel. + return coord_mapping.output_bounds.size().GetArea() * 4; +} + +CachedSoftwareRasterizer::CachedSoftwareRasterizer(SbBlitterDevice device, + SbBlitterContext context, + int cache_capacity) + : cache_capacity_(cache_capacity), + device_(device), + context_(context), + software_rasterizer_(0), + cache_memory_usage_( + "Memory.CachedSoftwareRasterizer.CacheUsage", 0, + "Total memory occupied by cached software-rasterized surfaces."), + cache_frame_usage_( + "Memory.CachedSoftwareRasterizer.FrameCacheUsage", 0, + "Total memory occupied by cache software-rasterizer surfaces that " + "were referenced this frame.") {} + +CachedSoftwareRasterizer::~CachedSoftwareRasterizer() { + // Clean up any leftover surfaces. + for (CacheMap::iterator iter = surface_map_.begin(); + iter != surface_map_.end(); ++iter) { + DCHECK(iter->second.cached); + SbBlitterDestroySurface(iter->second.surface); + } +} + +void CachedSoftwareRasterizer::OnStartNewFrame() { + for (CacheMap::iterator iter = surface_map_.begin(); + iter != surface_map_.end();) { + CacheMap::iterator current = iter; + ++iter; + + // If the surface was referenced mark it as being unreferenced for the next + // frame. + if (current->second.referenced) { +#if defined(ENABLE_DEBUG_CONSOLE) + current->second.created = false; +#endif // defined(ENABLE_DEBUG_CONSOLE) + current->second.referenced = false; + } + } + + // Reset our current frame cache usage to 0 since this is the start of a new + // frame. + cache_frame_usage_ = 0; +} + +void CachedSoftwareRasterizer::PurgeUntilSpaceAvailable(int space_needed) { + while (space_needed + cache_memory_usage_.value() > cache_capacity_) { + CacheMap::iterator next_item = surface_map_.begin(); + + // We shouldn't call this function if it means we would have to purge + // elements that were referenced this frame. This is to avoid thrashing + // the cache, we would prefer to cache as much as we can in a frame, and + // then just not cache whatever we can't without cycling what's in the cache + // already. + DCHECK(!next_item->second.referenced); + cache_memory_usage_ -= next_item->second.GetEstimatedMemoryUsage(); + SbBlitterDestroySurface(next_item->second.surface); + surface_map_.erase(next_item); + } +} + +CachedSoftwareRasterizer::Surface CachedSoftwareRasterizer::GetSurface( + render_tree::Node* node, const Transform& transform) { + CacheMap::iterator found = surface_map_.find(node); + if (found != surface_map_.end()) { +#if SB_HAS(BILINEAR_FILTERING_SUPPORT) + if (found->second.scale.x() >= transform.scale().x() && + found->second.scale.y() >= transform.scale().y()) { +#else // SB_HAS(BILINEAR_FILTERING_SUPPORT) + if (found->second.scale.x() == transform.scale().x() && + found->second.scale.y() == transform.scale().y()) { +#endif + std::pair<render_tree::Node*, Surface> to_insert = + std::make_pair(node, found->second); + + // Move this surface's position in the queue to the front, since it was + // referenced. + to_insert.second.referenced = true; + surface_map_.erase(found); + surface_map_.insert(to_insert); + + cache_frame_usage_ += to_insert.second.GetEstimatedMemoryUsage(); + + return to_insert.second; + } + } + + Surface software_surface; + software_surface.referenced = true; +#if defined(ENABLE_DEBUG_CONSOLE) + software_surface.created = true; +#endif // defined(ENABLE_DEBUG_CONSOLE) + software_surface.node = node; + software_surface.surface = kSbBlitterInvalidSurface; + software_surface.cached = false; + + // We ensure that scale is at least 1. Since it is common to have animating + // scale between 0 and 1 (e.g. an item animating from 0 to 1 to "grow" into + // the scene), this ensures that rendered items will not look pixellated as + // they grow (e.g. if they were cached at scale 0.2, they would be stretched + // x5 if the scale were to animate to 1.0). + if (transform.scale().x() == 0.0f || transform.scale().y() == 0.0f) { + // There won't be anything to render if the transform squishes one dimension + // to 0. + return software_surface; + } + DCHECK_LT(0, transform.scale().x()); + DCHECK_LT(0, transform.scale().y()); + Transform scaled_transform(transform); +// If bilinear filtering is not supported, do not rely on the rasterizer to +// scale down for us, it results in too many artifacts. +#if SB_HAS(BILINEAR_FILTERING_SUPPORT) + math::Vector2dF apply_scale(1.0f, 1.0f); + if (transform.scale().x() < 1.0f) { + apply_scale.set_x(1.0f / transform.scale().x()); + } + if (transform.scale().y() < 1.0f) { + apply_scale.set_y(1.0f / transform.scale().y()); + } + scaled_transform.ApplyScale(apply_scale); +#endif // SB_HAS(BILINEAR_FILTERING_SUPPORT) + + software_surface.scale = scaled_transform.scale(); + + common::OffscreenRenderCoordinateMapping coord_mapping = + common::GetOffscreenRenderCoordinateMapping(node->GetBounds(), + scaled_transform.ToMatrix(), + base::optional<math::Rect>()); + + software_surface.coord_mapping = coord_mapping; + + if (coord_mapping.output_bounds.IsEmpty()) { + // There's nothing to render if the bounds are 0. + return software_surface; + } + + DCHECK_GE(0.001f, std::abs(1.0f - + scaled_transform.scale().x() * + coord_mapping.output_post_scale.x())); + DCHECK_GE(0.001f, std::abs(1.0f - + scaled_transform.scale().y() * + coord_mapping.output_post_scale.y())); + + SkImageInfo output_image_info = SkImageInfo::MakeN32( + coord_mapping.output_bounds.width(), coord_mapping.output_bounds.height(), + kPremul_SkAlphaType); + + // Allocate the pixels for the output image. + SbBlitterPixelDataFormat blitter_pixel_data_format = + SkiaToBlitterPixelFormat(output_image_info.colorType()); + DCHECK(SbBlitterIsPixelFormatSupportedByPixelData(device_, + blitter_pixel_data_format)); + SbBlitterPixelData pixel_data = SbBlitterCreatePixelData( + device_, coord_mapping.output_bounds.width(), + coord_mapping.output_bounds.height(), blitter_pixel_data_format); + CHECK(SbBlitterIsPixelDataValid(pixel_data)); + + SkBitmap bitmap; + bitmap.installPixels(output_image_info, + SbBlitterGetPixelDataPointer(pixel_data), + SbBlitterGetPixelDataPitchInBytes(pixel_data)); + + // Setup our Skia canvas that we will be using as the target for all CPU Skia + // output. + SkCanvas canvas(bitmap); + canvas.clear(SkColorSetARGB(0, 0, 0, 0)); + + Transform sub_render_transform(coord_mapping.sub_render_transform); + + // Now setup our canvas so that the render tree will be rendered to the top + // left corner instead of at node->GetBounds().origin(). + canvas.translate(sub_render_transform.translate().x(), + sub_render_transform.translate().y()); + // And finally set the scale on our target canvas to match that of the current + // |transform|. + canvas.scale(sub_render_transform.scale().x(), + sub_render_transform.scale().y()); + + // Use the Skia software rasterizer to render our subtree. + software_rasterizer_.Submit(node, &canvas); + + // Create a surface out of the now populated pixel data. + software_surface.surface = + SbBlitterCreateSurfaceFromPixelData(device_, pixel_data); + + if (software_surface.GetEstimatedMemoryUsage() + cache_frame_usage_.value() <= + cache_capacity_) { + software_surface.cached = true; + cache_frame_usage_ += software_surface.GetEstimatedMemoryUsage(); + + if (found != surface_map_.end()) { + // This surface may have already been in the cache if it was in there + // with a different scale. In that case, replace the old one. + cache_memory_usage_ -= found->second.GetEstimatedMemoryUsage(); + surface_map_.erase(found); + } + + PurgeUntilSpaceAvailable(software_surface.GetEstimatedMemoryUsage()); + + std::pair<CacheMap::iterator, bool> inserted = + surface_map_.insert(std::make_pair(node, software_surface)); + cache_memory_usage_ += software_surface.GetEstimatedMemoryUsage(); + } + + return software_surface; +} + +} // namespace blitter +} // namespace rasterizer +} // namespace renderer +} // namespace cobalt + +#endif // #if SB_HAS(BLITTER)
diff --git a/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h new file mode 100644 index 0000000..10d76e9 --- /dev/null +++ b/src/cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h
@@ -0,0 +1,156 @@ +/* + * Copyright 2016 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_RENDERER_RASTERIZER_BLITTER_CACHED_SOFTWARE_RASTERIZER_H_ +#define COBALT_RENDERER_RASTERIZER_BLITTER_CACHED_SOFTWARE_RASTERIZER_H_ + +#include "base/containers/linked_hash_map.h" +#include "base/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "cobalt/base/c_val.h" +#include "cobalt/render_tree/node.h" +#include "cobalt/renderer/rasterizer/blitter/render_state.h" +#include "cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h" +#include "cobalt/renderer/rasterizer/skia/software_rasterizer.h" +#include "starboard/blitter.h" + +#if SB_HAS(BLITTER) + +namespace cobalt { +namespace renderer { +namespace rasterizer { +namespace blitter { + +// This class is responsible for creating SbBlitterSurfaces and software +// rasterizing render tree nodes to them to be used by clients. The class +// manages a cache of software surfaces in order to avoid duplicate work. Its +// main public interface is for clients to construct +// CachedSoftwareRasterizer::SurfaceReference objects from which they can +// extract the software-rasterized surface. Internally, software rasterization +// is handled via a skia::SoftwareRasterizer object. +class CachedSoftwareRasterizer { + public: + struct Surface { + // The actual surface containing rendered data. + SbBlitterSurface surface; + + // True if this surface was referenced at least once since the last time + // OnStartNewFrame() was called. + bool referenced; + +#if defined(ENABLE_DEBUG_CONSOLE) + // True if OnStartNewFrame() has not been called since this surface was + // created. + bool created; +#endif // defined(ENABLE_DEBUG_CONSOLE) + + // Transform information detailing how the surface should be output to + // the display such that sub-pixel alignments are respected. + common::OffscreenRenderCoordinateMapping coord_mapping; + + // A duplicate of the key, though as a smart pointer, to keep a reference + // to the node alive. + scoped_refptr<render_tree::Node> node; + + // Is this surface cached or not. + bool cached; + + // The scale used for rasterization when the surface was cached. + math::Vector2dF scale; + + size_t GetEstimatedMemoryUsage() const; + }; + + CachedSoftwareRasterizer(SbBlitterDevice device, SbBlitterContext context, + int cache_capacity); + ~CachedSoftwareRasterizer(); + + // Should be called once per frame. This method will remove from the cache + // anything that hasn't been referenced in the last frame. + void OnStartNewFrame(); + + // A SurfaceReference is the mechanism through which clients can request + // renders from the CachedSoftwareRasterizer. The main reason that we don't + // make GetSurface() the direct public interface is so that we can decide + // to rasterize something without caching it, in which case it should be + // cleaned up when the reference to it expires. + class SurfaceReference { + public: + // The |transform| parameter should be the transform that the node will + // ultimately have applied to it, and is used for ensuring that the rendered + // result is sub-pixel aligned properly. + SurfaceReference(CachedSoftwareRasterizer* rasterizer, + render_tree::Node* node, const Transform& transform) + : surface_(rasterizer->GetSurface(node, transform)) {} + ~SurfaceReference() { + // If the surface returned to us was not actually cached, then destroy + // it here when the surface reference is destroyed. + if (!surface_.cached && SbBlitterIsSurfaceValid(surface_.surface)) { + SbBlitterDestroySurface(surface_.surface); + } + } + const Surface& surface() const { return surface_; } + + private: + Surface surface_; + + DISALLOW_COPY_AND_ASSIGN(SurfaceReference); + }; + + Surface GetSurface(render_tree::Node* node, const Transform& transform); + + render_tree::ResourceProvider* GetResourceProvider() { + return software_rasterizer_.GetResourceProvider(); + } + + private: + typedef base::linked_hash_map<render_tree::Node*, Surface> CacheMap; + + // Release surfaces until we have |space_needed| free bytes in the cache. + // This function will never release surfaces that were referenced this frame. + // It is an error to call this function if it is impossible to purge + // unreferenced surfaces until the desired amount of free space is available. + void PurgeUntilSpaceAvailable(int space_needed); + + // The cache, mapping input render_tree::Node references to cached surfaces. + CacheMap surface_map_; + + const int cache_capacity_; + + // The Blitter device/context that all image allocation/blitting operations + // will occur within. + SbBlitterDevice device_; + SbBlitterContext context_; + + // The internal rasterizer used to actually render nodes. + skia::SoftwareRasterizer software_rasterizer_; + + // The amount of memory currently consumed by the surfaces populating the + // cache. + base::CVal<base::cval::SizeInBytes> cache_memory_usage_; + + // Cache memory used this frame only. + base::CVal<base::cval::SizeInBytes> cache_frame_usage_; +}; + +} // namespace blitter +} // namespace rasterizer +} // namespace renderer +} // namespace cobalt + +#endif // #if SB_HAS(BLITTER) + +#endif // COBALT_RENDERER_RASTERIZER_BLITTER_CACHED_SOFTWARE_RASTERIZER_H_
diff --git a/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.cc b/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.cc index 7206ba2..29a960a 100644 --- a/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.cc +++ b/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.cc
@@ -23,11 +23,9 @@ namespace rasterizer { namespace blitter { -namespace { int RoundToInt(float value) { return static_cast<int>(std::floor(value + 0.5f)); } -} // namespace math::Rect RectFToRect(const math::RectF& rectf) { // We convert from floating point to integer in such a way that two boxes
diff --git a/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h b/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h index 29afcec..d13b77d 100644 --- a/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h +++ b/src/cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h
@@ -28,6 +28,7 @@ namespace rasterizer { namespace blitter { +int RoundToInt(float value); math::Rect RectFToRect(const math::RectF& rectf); inline SbBlitterRect RectToBlitterRect(const math::Rect& rect) {
diff --git a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc index 2868926..cfca7ab 100644 --- a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc +++ b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.cc
@@ -16,11 +16,18 @@ #include "cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h" +#include <string> + #include "base/debug/trace_event.h" #include "base/threading/thread_checker.h" +#if defined(ENABLE_DEBUG_CONSOLE) +#include "cobalt/base/console_commands.h" +#endif #include "cobalt/render_tree/resource_provider_stub.h" #include "cobalt/renderer/backend/blitter/graphics_context.h" #include "cobalt/renderer/backend/blitter/render_target.h" +#include "cobalt/renderer/frame_rate_throttler.h" +#include "cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h" #include "cobalt/renderer/rasterizer/blitter/render_state.h" #include "cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h" #include "cobalt/renderer/rasterizer/blitter/resource_provider.h" @@ -39,43 +46,82 @@ public: explicit Impl(backend::GraphicsContext* graphics_context, int scratch_surface_size_in_bytes, - int surface_cache_size_in_bytes); + int surface_cache_size_in_bytes, + int software_surface_cache_size_in_bytes); ~Impl(); void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target, - int options); + const Options& options); render_tree::ResourceProvider* GetResourceProvider(); private: +#if defined(ENABLE_DEBUG_CONSOLE) + void OnToggleHighlightSoftwareDraws(const std::string& message); +#endif + void SetupLastFrameSurface(int width, int height); + base::ThreadChecker thread_checker_; backend::GraphicsContextBlitter* context_; - skia::SoftwareRasterizer software_rasterizer_; scoped_ptr<render_tree::ResourceProvider> resource_provider_; int64 submit_count_; + // We maintain a "final results" surface that mirrors the display buffer. + // This way, we can rerender only the dirty parts of the screen to this + // |current_frame_| buffer and then blit that to the display. + SbBlitterSurface current_frame_; + ScratchSurfaceCache scratch_surface_cache_; base::optional<SurfaceCacheDelegate> surface_cache_delegate_; base::optional<common::SurfaceCache> surface_cache_; + + CachedSoftwareRasterizer software_surface_cache_; + + FrameRateThrottler frame_rate_throttler_; + +#if defined(ENABLE_DEBUG_CONSOLE) + // Debug command to toggle cache highlights to help visualize which nodes + // are being cached. + bool toggle_highlight_software_draws_; + base::ConsoleCommandManager::CommandHandler + toggle_highlight_software_draws_command_handler_; +#endif }; HardwareRasterizer::Impl::Impl(backend::GraphicsContext* graphics_context, int scratch_surface_size_in_bytes, - int surface_cache_size_in_bytes) + int surface_cache_size_in_bytes, + int software_surface_cache_size_in_bytes) : context_(base::polymorphic_downcast<backend::GraphicsContextBlitter*>( graphics_context)), - software_rasterizer_(0), submit_count_(0), + current_frame_(kSbBlitterInvalidSurface), scratch_surface_cache_(context_->GetSbBlitterDevice(), context_->GetSbBlitterContext(), - scratch_surface_size_in_bytes) { + scratch_surface_size_in_bytes), + software_surface_cache_(context_->GetSbBlitterDevice(), + context_->GetSbBlitterContext(), + software_surface_cache_size_in_bytes) +#if defined(ENABLE_DEBUG_CONSOLE) + , + toggle_highlight_software_draws_(false), + toggle_highlight_software_draws_command_handler_( + "toggle_highlight_software_draws", + base::Bind(&HardwareRasterizer::Impl::OnToggleHighlightSoftwareDraws, + base::Unretained(this)), + "Highlights regions where software rasterization is occurring.", + "Toggles whether all software rasterized elements will appear as a " + "green rectangle or not. This can be used to identify where in a " + "scene software rasterization is occurring.") +#endif // defined(ENABLE_DEBUG_CONSOLE) +{ resource_provider_ = scoped_ptr<render_tree::ResourceProvider>( new ResourceProvider(context_->GetSbBlitterDevice(), - software_rasterizer_.GetResourceProvider())); + software_surface_cache_.GetResourceProvider())); if (surface_cache_size_in_bytes > 0) { surface_cache_delegate_.emplace(context_->GetSbBlitterDevice(), @@ -86,11 +132,20 @@ } } -HardwareRasterizer::Impl::~Impl() {} +HardwareRasterizer::Impl::~Impl() { SbBlitterDestroySurface(current_frame_); } + +#if defined(ENABLE_DEBUG_CONSOLE) +void HardwareRasterizer::Impl::OnToggleHighlightSoftwareDraws( + const std::string& message) { + UNREFERENCED_PARAMETER(message); + toggle_highlight_software_draws_ = !toggle_highlight_software_draws_; +} +#endif void HardwareRasterizer::Impl::Submit( const scoped_refptr<render_tree::Node>& render_tree, - const scoped_refptr<backend::RenderTarget>& render_target, int options) { + const scoped_refptr<backend::RenderTarget>& render_target, + const Options& options) { TRACE_EVENT0("cobalt::renderer", "Rasterizer::Submit()"); int width = render_target->GetSize().width(); @@ -102,8 +157,12 @@ base::polymorphic_downcast<backend::RenderTargetBlitter*>( render_target.get()); - CHECK(SbBlitterSetRenderTarget(context, - render_target_blitter->GetSbRenderTarget())); + if (!SbBlitterIsSurfaceValid(current_frame_)) { + SetupLastFrameSurface(width, height); + } + + CHECK(SbBlitterSetRenderTarget( + context, SbBlitterGetRenderTargetFromSurface(current_frame_))); // Update our surface cache to do per-frame calculations such as deciding // which render tree nodes are candidates for caching in this upcoming @@ -112,12 +171,16 @@ surface_cache_->Frame(); } + software_surface_cache_.OnStartNewFrame(); + // Clear the background before proceeding if the clear option is set. // We also clear if this is one of the first 3 submits. This is for security // purposes, so that despite the Blitter API implementation, we ensure that // if the output buffer is not completely rendered to, data from a previous // process cannot leak in. - if (options & Rasterizer::kSubmitOptions_Clear || submit_count_ < 3) { + bool cleared = false; + if (options.flags & Rasterizer::kSubmitFlags_Clear || submit_count_ < 3) { + cleared = true; CHECK(SbBlitterSetBlending(context, false)); CHECK(SbBlitterSetColor(context, SbBlitterColorFromRGBA(0, 0, 0, 0))); CHECK(SbBlitterFillRect(context, SbBlitterMakeRect(0, 0, width, height))); @@ -127,20 +190,41 @@ TRACE_EVENT0("cobalt::renderer", "VisitRenderTree"); // Visit the render tree with our Blitter API visitor. + BoundsStack start_bounds(context_->GetSbBlitterContext(), + math::Rect(render_target_blitter->GetSize())); + if (options.dirty && !cleared) { + // If a dirty rectangle was specified, limit our redrawing to within it. + start_bounds.Push(*options.dirty); + } + + RenderState initial_render_state( + SbBlitterGetRenderTargetFromSurface(current_frame_), Transform(), + start_bounds); +#if defined(ENABLE_DEBUG_CONSOLE) + initial_render_state.highlight_software_draws = + toggle_highlight_software_draws_; +#endif // defined(ENABLE_DEBUG_CONSOLE) RenderTreeNodeVisitor visitor( context_->GetSbBlitterDevice(), context_->GetSbBlitterContext(), - RenderState(render_target_blitter->GetSbRenderTarget(), Transform(), - BoundsStack(context_->GetSbBlitterContext(), - math::Rect(render_target_blitter->GetSize()))), - &software_rasterizer_, &scratch_surface_cache_, + initial_render_state, &scratch_surface_cache_, surface_cache_delegate_ ? &surface_cache_delegate_.value() : NULL, - surface_cache_ ? &surface_cache_.value() : NULL); + surface_cache_ ? &surface_cache_.value() : NULL, + &software_surface_cache_); render_tree->Accept(&visitor); } // Finally flip the surface to make visible the rendered results. + CHECK(SbBlitterSetRenderTarget(context, + render_target_blitter->GetSbRenderTarget())); + CHECK(SbBlitterSetBlending(context, false)); + CHECK(SbBlitterSetModulateBlitsWithColor(context, false)); + CHECK(SbBlitterBlitRectToRect(context, current_frame_, + SbBlitterMakeRect(0, 0, width, height), + SbBlitterMakeRect(0, 0, width, height))); CHECK(SbBlitterFlushContext(context)); + frame_rate_throttler_.EndInterval(); render_target_blitter->Flip(); + frame_rate_throttler_.BeginInterval(); ++submit_count_; } @@ -149,17 +233,26 @@ return resource_provider_.get(); } +void HardwareRasterizer::Impl::SetupLastFrameSurface(int width, int height) { + current_frame_ = + SbBlitterCreateRenderTargetSurface(context_->GetSbBlitterDevice(), width, + height, kSbBlitterSurfaceFormatRGBA8); +} + HardwareRasterizer::HardwareRasterizer( backend::GraphicsContext* graphics_context, - int scratch_surface_size_in_bytes, int surface_cache_size_in_bytes) + int scratch_surface_size_in_bytes, int surface_cache_size_in_bytes, + int software_surface_cache_size_in_bytes) : impl_(new Impl(graphics_context, scratch_surface_size_in_bytes, - surface_cache_size_in_bytes)) {} + surface_cache_size_in_bytes, + software_surface_cache_size_in_bytes)) {} HardwareRasterizer::~HardwareRasterizer() {} void HardwareRasterizer::Submit( const scoped_refptr<render_tree::Node>& render_tree, - const scoped_refptr<backend::RenderTarget>& render_target, int options) { + const scoped_refptr<backend::RenderTarget>& render_target, + const Options& options) { return impl_->Submit(render_tree, render_target, options); }
diff --git a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h index abf4d4e..5ab8fd5 100644 --- a/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h +++ b/src/cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h
@@ -39,14 +39,15 @@ public: explicit HardwareRasterizer(backend::GraphicsContext* graphics_context, int scratch_surface_size_in_bytes, - int surface_cache_size_in_bytes); + int surface_cache_size_in_bytes, + int software_surface_cache_size_in_bytes); virtual ~HardwareRasterizer(); // Consume the render tree and output the results to the render target passed // into the constructor. void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target, - int options) OVERRIDE; + const Options& options) OVERRIDE; render_tree::ResourceProvider* GetResourceProvider() OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/blitter/image.cc b/src/cobalt/renderer/rasterizer/blitter/image.cc index 3e8ece5..af46382 100644 --- a/src/cobalt/renderer/rasterizer/blitter/image.cc +++ b/src/cobalt/renderer/rasterizer/blitter/image.cc
@@ -38,7 +38,8 @@ RenderTreePixelFormatToBlitter(pixel_format))), descriptor_(size, pixel_format, alpha_format, SbBlitterGetPixelDataPitchInBytes(pixel_data_)) { - CHECK_EQ(render_tree::kAlphaFormatPremultiplied, alpha_format); + CHECK(alpha_format == render_tree::kAlphaFormatPremultiplied || + alpha_format == render_tree::kAlphaFormatOpaque); CHECK(SbBlitterIsPixelDataValid(pixel_data_)); } @@ -63,9 +64,22 @@ surface_ = SbBlitterCreateSurfaceFromPixelData(image_data->device(), image_data->TakePixelData()); CHECK(SbBlitterIsSurfaceValid(surface_)); + + is_opaque_ = image_data->GetDescriptor().alpha_format == + render_tree::kAlphaFormatOpaque; } -void SinglePlaneImage::EnsureInitialized() {} +SinglePlaneImage::SinglePlaneImage(SbBlitterSurface surface, bool is_opaque) + : surface_(surface), is_opaque_(is_opaque) { + CHECK(SbBlitterIsSurfaceValid(surface_)); + SbBlitterSurfaceInfo info; + if (!SbBlitterGetSurfaceInfo(surface_, &info)) { + NOTREACHED(); + } + size_ = math::Size(info.width, info.height); +} + +bool SinglePlaneImage::EnsureInitialized() { return false; } const SkBitmap& SinglePlaneImage::GetBitmap() const { // This function will only ever get called if the Skia software renderer needs
diff --git a/src/cobalt/renderer/rasterizer/blitter/image.h b/src/cobalt/renderer/rasterizer/blitter/image.h index 7275225..65229ab 100644 --- a/src/cobalt/renderer/rasterizer/blitter/image.h +++ b/src/cobalt/renderer/rasterizer/blitter/image.h
@@ -71,25 +71,30 @@ class SinglePlaneImage : public skia::SinglePlaneImage { public: explicit SinglePlaneImage(scoped_ptr<ImageData> image_data); + SinglePlaneImage(SbBlitterSurface surface, bool is_opaque); const math::Size& GetSize() const OVERRIDE { return size_; } SbBlitterSurface surface() const { return surface_; } // Overrides from skia::SinglePlaneImage. - void EnsureInitialized() OVERRIDE; + bool EnsureInitialized() OVERRIDE; // When GetBitmap() is called on a blitter::SinglePlaneImage for the first // time, we do a one-time download of the pixel data from the Blitter API // surface into a SkBitmap. const SkBitmap& GetBitmap() const OVERRIDE; + bool IsOpaque() const OVERRIDE { return is_opaque_; } + private: ~SinglePlaneImage() OVERRIDE; math::Size size_; SbBlitterSurface surface_; + bool is_opaque_; + // This field is populated when GetBitmap() is called for the first time, and // after that is never modified. mutable base::optional<SkBitmap> bitmap_;
diff --git a/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc new file mode 100644 index 0000000..1dc7e96 --- /dev/null +++ b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.cc
@@ -0,0 +1,311 @@ +/* + * Copyright 2016 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/renderer/rasterizer/blitter/linear_gradient.h" + +#include <algorithm> + +#include "cobalt/base/polymorphic_downcast.h" +#include "cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h" +#include "cobalt/renderer/rasterizer/blitter/render_state.h" +#include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h" +#include "cobalt/renderer/rasterizer/skia/conversions.h" + +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/include/effects/SkGradientShader.h" + +#if SB_HAS(BLITTER) + +namespace { + +using cobalt::render_tree::ColorRGBA; +using cobalt::render_tree::ColorStopList; +using cobalt::render_tree::LinearGradientBrush; +using cobalt::renderer::rasterizer::blitter::RenderState; +using cobalt::renderer::rasterizer::blitter::SkiaToBlitterPixelFormat; +using cobalt::renderer::rasterizer::blitter::RectFToRect; +using cobalt::renderer::rasterizer::skia::SkiaColorStops; + +int GetPixelOffsetInBytes(const SkImageInfo& image_info, + const SbBlitterPixelData& pixel_data) { + if (image_info.width() == 1) + return SbBlitterGetPixelDataPitchInBytes(pixel_data); + + SbBlitterPixelDataFormat pixel_format = + SkiaToBlitterPixelFormat(image_info.colorType()); + return SbBlitterBytesPerPixelForFormat(pixel_format); +} + +void ColorStopToPixelData(SbBlitterPixelDataFormat pixel_format, + const ColorRGBA& color_stop, uint8_t* pixel_data_out, + uint8_t* pixel_data_out_end) { + DCHECK(pixel_data_out_end >= + (pixel_data_out + SbBlitterBytesPerPixelForFormat(pixel_format))); + uint8_t final_b(color_stop.rgb8_b()); + uint8_t final_g(color_stop.rgb8_g()); + uint8_t final_r(color_stop.rgb8_r()); + uint8_t final_a(color_stop.rgb8_a()); + + switch (pixel_format) { + case kSbBlitterPixelDataFormatARGB8: + pixel_data_out[0] = final_a; + pixel_data_out[1] = final_r; + pixel_data_out[2] = final_g; + pixel_data_out[3] = final_b; + break; + case kSbBlitterPixelDataFormatBGRA8: + pixel_data_out[0] = final_b; + pixel_data_out[1] = final_g; + pixel_data_out[2] = final_r; + pixel_data_out[3] = final_a; + break; + case kSbBlitterPixelDataFormatRGBA8: + pixel_data_out[0] = final_r; + pixel_data_out[1] = final_g; + pixel_data_out[2] = final_b; + pixel_data_out[3] = final_a; + break; + default: + NOTREACHED() << "Unknown or unsupported pixel format." << pixel_format; + break; + } +} + +// This function uses Skia to render a gradient into the pixel data pointed +// at pixel_data_begin. This function is intended as a fallback in case when +// RenderOptimizedLinearGradient is unable to render the gradient. +void RenderComplexLinearGradient(const LinearGradientBrush& brush, + const ColorStopList& color_stops, int width, + int height, const SkImageInfo& image_info, + uint8_t* pixel_data_begin, + int pitch_in_bytes) { + SkBitmap bitmap; + bitmap.installPixels(image_info, pixel_data_begin, pitch_in_bytes); + SkCanvas canvas(bitmap); + canvas.clear(SkColorSetARGB(0, 0, 0, 0)); + + SkPoint points[2]; + points[0].fX = 0; + points[0].fY = 0; + points[1].fX = 0; + points[1].fY = 0; + + // The -1 offset is easiest to think about in a 2 pixel case: + // If width = 2, then the ending coordinate should be 1. + if (brush.IsHorizontal()) { + if (brush.dest().x() < brush.source().x()) { + points[0].fX = width - 1; // Right to Left. + } else { + points[1].fX = width - 1; // Left to Right. + } + } else { + if (brush.dest().y() < brush.source().y()) { + points[0].fY = height - 1; // High to Low. + } else { + points[1].fY = height - 1; // Low to High. + } + } + + SkiaColorStops skia_color_stops(color_stops); + SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear( + points, skia_color_stops.colors.data(), skia_color_stops.positions.data(), + skia_color_stops.size(), SkShader::kClamp_TileMode, + SkGradientShader::kInterpolateColorsInPremul_Flag, NULL)); + SkPaint paint; + paint.setShader(shader); + canvas.drawPaint(paint); +} + +// This function tries to render a "simple" gradient into the pixel data pointed +// to in the range [pixel_data_begin, pixel_data_end). Simple gradients here +// are defined as has following properties: +// 1. Vertical or horizontal gradient. +// 2. Two color stops only -- one at position 0, and one at position 1. +// +// Having these conditions greatly simplifies the logic, and allows us to avoid +// the overhead of dealing with Skia. +// +// Returns: true if it could successfully render, false if optimal conditions +// are not met. If false is returned, nothing is written to the pixel data +// buffer. +bool RenderSimpleGradient(const LinearGradientBrush& brush, + const ColorStopList& color_stops, int width, + int height, SbBlitterPixelDataFormat pixel_format, + uint8_t* pixel_data_begin, uint8_t* pixel_data_end, + int pixel_offset_in_bytes) { + if (color_stops.size() != 2) { + return false; + } + + const float small_value(1E-6); + if ((color_stops[0].position < -small_value) || + (color_stops[0].position > small_value)) { + return false; + } + if ((color_stops[1].position < 1 - small_value) || + (color_stops[1].position > 1 + small_value)) { + return false; + } + + const int number_pixels = width * height; + + if ((number_pixels < 2) || (width < 0) || (height < 0)) { + return false; + } + + ColorRGBA start_color = color_stops[0].color; + ColorRGBA end_color = color_stops[1].color; + + // Swap start and end colors if the gradient direction is right to left or + // down to up. + if (brush.IsHorizontal()) { + if (brush.dest().x() < brush.source().x()) { + std::swap(start_color, end_color); + } + } else { + if (brush.dest().y() < brush.source().y()) { + std::swap(start_color, end_color); + } + } + + ColorRGBA color_step((end_color - start_color) / (number_pixels - 1)); + // We add the color_stop * 0.5f to match pixel positioning in software Skia. + ColorRGBA current_color(start_color + color_step * 0.5f); + ColorStopToPixelData(pixel_format, current_color, pixel_data_begin, + pixel_data_end); + + uint8_t* second_pixel = pixel_data_begin + pixel_offset_in_bytes; + uint8_t* last_pixel = + (pixel_data_begin + (number_pixels - 1) * pixel_offset_in_bytes); + for (uint8_t* current_pixel = second_pixel; current_pixel != last_pixel; + current_pixel += pixel_offset_in_bytes) { + current_color += color_step; + ColorStopToPixelData(pixel_format, current_color, current_pixel, + pixel_data_end); + } + + ColorStopToPixelData(pixel_format, end_color, last_pixel, pixel_data_end); + + return true; +} + +void RenderOptimizedLinearGradient(SbBlitterDevice device, + SbBlitterContext context, + const RenderState& render_state, + cobalt::math::RectF rect, + const LinearGradientBrush& brush) { + if ((rect.width() == 0) && (rect.height() == 0)) return; + + DCHECK(brush.IsVertical() || brush.IsHorizontal()); + + // The main strategy here is to create a 1D image, and then calculate + // the gradient values in software. If the gradient is simple, this can be + // one with optimized function (RenderSimpleGradient), which avoids calling + // Skia (and thus is faster). Otherwise, we call RenderComplexLinearGradient, + // which uses Skia. + + // One a gradient is created, a SbBlitterSurface is created and a rectangle + // is blitted using the blitter API. + int width = brush.IsHorizontal() ? rect.width() : 1; + int height = brush.IsVertical() ? rect.height() : 1; + + SkImageInfo image_info = SkImageInfo::MakeN32Premul(width, height); + + SbBlitterPixelDataFormat pixel_format = + SkiaToBlitterPixelFormat(image_info.colorType()); + + if (!SbBlitterIsPixelFormatSupportedByPixelData(device, pixel_format)) { + NOTREACHED() << "Pixel Format is not supported by Pixel Data"; + return; + } + + SbBlitterPixelData pixel_data = + SbBlitterCreatePixelData(device, width, height, pixel_format); + + int bytes_per_pixel = GetPixelOffsetInBytes(image_info, pixel_data); + int pitch_in_bytes = SbBlitterGetPixelDataPitchInBytes(pixel_data); + + uint8_t* pixel_data_begin = + static_cast<uint8_t*>(SbBlitterGetPixelDataPointer(pixel_data)); + uint8_t* pixel_data_end = pixel_data_begin + pitch_in_bytes * height; + + if (pixel_data) { + const ColorStopList& color_stops(brush.color_stops()); + + int pixel_offset_in_bytes = (width == 1) ? pitch_in_bytes : bytes_per_pixel; + bool render_successful = RenderSimpleGradient( + brush, color_stops, width, height, pixel_format, pixel_data_begin, + pixel_data_end, pixel_offset_in_bytes); + if (!render_successful) { + RenderComplexLinearGradient(brush, color_stops, width, height, image_info, + pixel_data_begin, pitch_in_bytes); + } + + SbBlitterSurface surface = + SbBlitterCreateSurfaceFromPixelData(device, pixel_data); + + if (surface) { + SbBlitterSetBlending(context, true); + SbBlitterSetModulateBlitsWithColor(context, false); + cobalt::math::Rect transformed_rect = + RectFToRect(render_state.transform.TransformRect(rect)); + SbBlitterRect source_rect = SbBlitterMakeRect(0, 0, width, height); + SbBlitterRect dest_rect = SbBlitterMakeRect( + transformed_rect.x(), transformed_rect.y(), transformed_rect.width(), + transformed_rect.height()); + SbBlitterBlitRectToRect(context, surface, source_rect, dest_rect); + + SbBlitterDestroySurface(surface); + } + } +} +} // namespace + +namespace cobalt { +namespace renderer { +namespace rasterizer { +namespace blitter { + +using render_tree::LinearGradientBrush; + +bool RenderLinearGradient(SbBlitterDevice device, SbBlitterContext context, + const RenderState& render_state, + const render_tree::RectNode& rect_node) { + DCHECK(rect_node.data().background_brush); + const LinearGradientBrush* const linear_gradient_brush = + base::polymorphic_downcast<LinearGradientBrush*>( + rect_node.data().background_brush.get()); + if (!linear_gradient_brush) return false; + + // Currently, only vertical and horizontal gradients are accelerated. + if ((linear_gradient_brush->IsVertical() || + linear_gradient_brush->IsHorizontal()) == false) + return false; + + RenderOptimizedLinearGradient(device, context, render_state, + rect_node.data().rect, *linear_gradient_brush); + return true; +} + +} // namespace blitter +} // namespace rasterizer +} // namespace renderer +} // namespace cobalt + +#endif // #if SB_HAS(BLITTER)
diff --git a/src/cobalt/renderer/rasterizer/blitter/linear_gradient.h b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.h new file mode 100644 index 0000000..8340722 --- /dev/null +++ b/src/cobalt/renderer/rasterizer/blitter/linear_gradient.h
@@ -0,0 +1,46 @@ +/* + * Copyright 2016 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_RENDERER_RASTERIZER_BLITTER_LINEAR_GRADIENT_H_ +#define COBALT_RENDERER_RASTERIZER_BLITTER_LINEAR_GRADIENT_H_ + +#include "cobalt/math/rect_f.h" +#include "cobalt/render_tree/rect_node.h" +#include "cobalt/renderer/rasterizer/blitter/render_state.h" +#include "starboard/blitter.h" + +#ifndef SB_HAS_BLITTER +#define SB_HAS_BLITTER +#endif +#if SB_HAS(BLITTER) + +namespace cobalt { +namespace renderer { +namespace rasterizer { +namespace blitter { + +bool RenderLinearGradient(SbBlitterDevice device, SbBlitterContext context, + const RenderState& render_state, + const render_tree::RectNode& rect_node); + +} // namespace blitter +} // namespace rasterizer +} // namespace renderer +} // namespace cobalt + +#endif // #if SB_HAS(BLITTER) + +#endif // COBALT_RENDERER_RASTERIZER_BLITTER_LINEAR_GRADIENT_H_
diff --git a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp index 8c75fd1..b5e0bec 100644 --- a/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp +++ b/src/cobalt/renderer/rasterizer/blitter/rasterizer.gyp
@@ -38,9 +38,11 @@ 'type': 'static_library', 'sources': [ + 'cached_software_rasterizer.cc', 'cobalt_blitter_conversions.cc', 'hardware_rasterizer.cc', 'image.cc', + 'linear_gradient.cc', 'render_state.cc', 'render_tree_blitter_conversions.cc', 'render_tree_node_visitor.cc', @@ -80,4 +82,4 @@ ], }, ], -} \ No newline at end of file +}
diff --git a/src/cobalt/renderer/rasterizer/blitter/render_state.h b/src/cobalt/renderer/rasterizer/blitter/render_state.h index 5b6f683..b0a828a 100644 --- a/src/cobalt/renderer/rasterizer/blitter/render_state.h +++ b/src/cobalt/renderer/rasterizer/blitter/render_state.h
@@ -25,6 +25,7 @@ #include "cobalt/math/rect.h" #include "cobalt/math/rect_f.h" #include "cobalt/math/vector2d_f.h" +#include "cobalt/render_tree/brush.h" #include "starboard/blitter.h" #if SB_HAS(BLITTER) @@ -118,11 +119,26 @@ const BoundsStack& bounds_stack) : render_target(render_target), transform(transform), - bounds_stack(bounds_stack) {} + bounds_stack(bounds_stack), + opacity(1.0f) +#if defined(ENABLE_DEBUG_CONSOLE) + , + highlight_software_draws(false) +#endif + { + } SbBlitterRenderTarget render_target; Transform transform; BoundsStack bounds_stack; + float opacity; + +#if defined(ENABLE_DEBUG_CONSOLE) + // If true, all software rasterization results are replaced with a green + // fill rectangle. Thus, if the software cache is used, one will see a flash + // of green every time something is registered with the cache. + bool highlight_software_draws; +#endif // defined(ENABLE_DEBUG_CONSOLE) }; } // namespace blitter
diff --git a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc index d0f5377..47dcf43 100644 --- a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc +++ b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.cc
@@ -17,6 +17,8 @@ #include "cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h" #include "base/bind.h" +#include "base/debug/trace_event.h" +#include "cobalt/base/polymorphic_downcast.h" #include "cobalt/math/matrix3_f.h" #include "cobalt/math/rect.h" #include "cobalt/math/rect_f.h" @@ -25,15 +27,24 @@ #include "cobalt/math/vector2d_f.h" #include "cobalt/renderer/rasterizer/blitter/cobalt_blitter_conversions.h" #include "cobalt/renderer/rasterizer/blitter/image.h" +#include "cobalt/renderer/rasterizer/blitter/linear_gradient.h" #include "cobalt/renderer/rasterizer/blitter/skia_blitter_conversions.h" #include "cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h" #include "starboard/blitter.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "third_party/skia/include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkImageInfo.h" #if SB_HAS(BLITTER) +// This define exists so that developers can quickly toggle it temporarily and +// obtain trace results for the render tree visit process here. In general +// though it slows down tracing too much to leave it enabled. +#define ENABLE_RENDER_TREE_VISITOR_TRACING 0 + +#if ENABLE_RENDER_TREE_VISITOR_TRACING +#define TRACE_EVENT0_IF_ENABLED(x) TRACE_EVENT0("cobalt::renderer", x) +#else +#define TRACE_EVENT0_IF_ENABLED(x) +#endif + namespace cobalt { namespace renderer { namespace rasterizer { @@ -47,23 +58,25 @@ using render_tree::Border; using render_tree::Brush; using render_tree::ColorRGBA; +using render_tree::ColorStop; +using render_tree::ColorStopList; +using render_tree::LinearGradientBrush; using render_tree::SolidColorBrush; using render_tree::ViewportFilter; RenderTreeNodeVisitor::RenderTreeNodeVisitor( SbBlitterDevice device, SbBlitterContext context, - const RenderState& render_state, - skia::SoftwareRasterizer* software_rasterizer, - ScratchSurfaceCache* scratch_surface_cache, + const RenderState& render_state, ScratchSurfaceCache* scratch_surface_cache, SurfaceCacheDelegate* surface_cache_delegate, - common::SurfaceCache* surface_cache) - : software_rasterizer_(software_rasterizer), - device_(device), + common::SurfaceCache* surface_cache, + CachedSoftwareRasterizer* software_surface_cache) + : device_(device), context_(context), render_state_(render_state), scratch_surface_cache_(scratch_surface_cache), surface_cache_delegate_(surface_cache_delegate), - surface_cache_(surface_cache) { + surface_cache_(surface_cache), + software_surface_cache_(software_surface_cache) { DCHECK_EQ(surface_cache_delegate_ == NULL, surface_cache_ == NULL); if (surface_cache_delegate_) { // Update our surface cache delegate to point to this render tree node @@ -77,8 +90,7 @@ void RenderTreeNodeVisitor::Visit( render_tree::CompositionNode* composition_node) { - common::SurfaceCache::Block cache_block(surface_cache_, composition_node); - if (cache_block.Cached()) return; + TRACE_EVENT0_IF_ENABLED("Visit(CompositionNode)"); const render_tree::CompositionNode::Children& children = composition_node->data().children(); @@ -99,7 +111,30 @@ render_state_.transform.ApplyOffset(-composition_node->data().offset()); } +namespace { +bool SourceCanRenderWithOpacity(render_tree::Node* source) { + if (source->GetTypeId() == base::GetTypeId<render_tree::ImageNode>() || + source->GetTypeId() == base::GetTypeId<render_tree::RectNode>()) { + return true; + } else if (source->GetTypeId() == + base::GetTypeId<render_tree::CompositionNode>()) { + // If we are a composition of valid sources, then we also allow + // rendering through a viewport here. + render_tree::CompositionNode* composition_node = + base::polymorphic_downcast<render_tree::CompositionNode*>(source); + typedef render_tree::CompositionNode::Children Children; + const Children& children = composition_node->data().children(); + if (children.size() == 1 && SourceCanRenderWithOpacity(children[0].get())) { + return true; + } + } + return false; +} +} // namespace + void RenderTreeNodeVisitor::Visit(render_tree::FilterNode* filter_node) { + TRACE_EVENT0_IF_ENABLED("Visit(FilterNode)"); + if (filter_node->data().blur_filter) { // The Starboard Blitter API does not support blur filters, so we fallback // to software for this. @@ -135,6 +170,16 @@ // we know that opacity is in the range (0, 1), exclusive. float opacity = filter_node->data().opacity_filter->opacity(); + if (SourceCanRenderWithOpacity(source)) { + float original_opacity = render_state_.opacity; + render_state_.opacity *= opacity; + + source->Accept(this); + + render_state_.opacity = original_opacity; + return; + } + // Render our source subtree to an offscreen surface, and then we will // re-render it to our main render target with an alpha value applied to it. scoped_ptr<OffscreenRender> offscreen_render = @@ -164,6 +209,8 @@ } void RenderTreeNodeVisitor::Visit(render_tree::ImageNode* image_node) { + TRACE_EVENT0_IF_ENABLED("Visit(ImageNode)"); + // All Blitter API images derive from skia::Image (so that they can be // compatible with the Skia software renderer), so we start here by casting // to skia::Image. @@ -202,8 +249,18 @@ -local_matrix.Get(1, 2) * image_size.height())); // Render the image. - SbBlitterSetBlending(context_, true); - SbBlitterSetModulateBlitsWithColor(context_, false); + if (render_state_.opacity < 1.0f) { + SbBlitterSetBlending(context_, true); + SbBlitterSetModulateBlitsWithColor(context_, true); + SbBlitterSetColor( + context_, + SbBlitterColorFromRGBA(255, 255, 255, + static_cast<int>(255 * render_state_.opacity))); + } else { + SbBlitterSetBlending(context_, !skia_image->IsOpaque()); + SbBlitterSetModulateBlitsWithColor(context_, false); + } + SbBlitterBlitRectToRectTiled( context_, blitter_image->surface(), RectFToBlitterRect(local_transform.TransformRect(RectF(image_size))), @@ -213,15 +270,14 @@ void RenderTreeNodeVisitor::Visit( render_tree::MatrixTransformNode* matrix_transform_node) { - common::SurfaceCache::Block cache_block(surface_cache_, - matrix_transform_node); - if (cache_block.Cached()) return; + TRACE_EVENT0_IF_ENABLED("Visit(MatrixTransformNode)"); const Matrix3F& transform = matrix_transform_node->data().transform; - if (transform.Get(1, 0) != 0 || transform.Get(0, 1) != 0) { - // The Starboard Blitter API does not support rotations/shears, so we must - // fallback to software in order to render the entire subtree. + if (transform.Get(1, 0) != 0 || transform.Get(0, 1) != 0 || + transform.Get(0, 0) < 0 || transform.Get(1, 1) < 0) { + // The Starboard Blitter API does not support rotations/shears/flips, so we + // must fallback to software in order to render the entire subtree. RenderWithSoftwareRenderer(matrix_transform_node); return; } @@ -240,20 +296,16 @@ void RenderTreeNodeVisitor::Visit( render_tree::PunchThroughVideoNode* punch_through_video_node) { + TRACE_EVENT0_IF_ENABLED("Visit(PunchThroughVideoNode)"); + SbBlitterRect blitter_rect = RectFToBlitterRect(render_state_.transform.TransformRect( punch_through_video_node->data().rect)); - if (punch_through_video_node->data().set_bounds_cb.is_null()) { - return; - } - bool render_punch_through = - punch_through_video_node->data().set_bounds_cb.Run( - math::Rect(blitter_rect.x, blitter_rect.y, blitter_rect.width, - blitter_rect.height)); - if (!render_punch_through) { - return; - } + punch_through_video_node->data().set_bounds_cb.Run( + math::Rect(blitter_rect.x, blitter_rect.y, blitter_rect.width, + blitter_rect.height)); + SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 0, 0, 0)); SbBlitterSetBlending(context_, false); SbBlitterFillRect(context_, blitter_rect); @@ -277,8 +329,14 @@ void RenderRectNodeBorder(SbBlitterContext context, ColorRGBA color, float left, float right, float top, float bottom, const RectF& rect) { - SbBlitterSetColor(context, RenderTreeToBlitterColor(color)); - SbBlitterSetBlending(context, true); + SbBlitterColor blitter_color = RenderTreeToBlitterColor(color); + SbBlitterSetColor(context, blitter_color); + + if (SbBlitterAFromColor(blitter_color) < 255) { + SbBlitterSetBlending(context, true); + } else { + SbBlitterSetBlending(context, false); + } // We draw four rectangles, one for each border edge. They have the following // layout: @@ -313,6 +371,8 @@ } // namespace void RenderTreeNodeVisitor::Visit(render_tree::RectNode* rect_node) { + TRACE_EVENT0_IF_ENABLED("Visit(RectNode)"); + if (rect_node->data().rounded_corners) { // We can't render rounded corners through the Blitter API. RenderWithSoftwareRenderer(rect_node); @@ -327,31 +387,43 @@ return; } } - if (rect_node->data().background_brush) { - if (rect_node->data().background_brush->GetTypeId() != - base::GetTypeId<SolidColorBrush>()) { - // We can only render solid color rectangles, if we have a more - // complicated brush (like gradients), fallback to software. - RenderWithSoftwareRenderer(rect_node); - return; - } - } const RectF& transformed_rect = render_state_.transform.TransformRect(rect_node->data().rect); - // Render the solid color fill, if a brush exists. if (rect_node->data().background_brush) { - SbBlitterSetBlending(context_, true); + base::TypeId background_brush_typeid( + rect_node->data().background_brush->GetTypeId()); - SolidColorBrush* solid_color_brush = - base::polymorphic_downcast<SolidColorBrush*>( - rect_node->data().background_brush.get()); - ColorRGBA color = solid_color_brush->color(); + if (background_brush_typeid == base::GetTypeId<SolidColorBrush>()) { + // Render the solid color fill, if a brush exists. + SolidColorBrush* solid_color_brush = + base::polymorphic_downcast<SolidColorBrush*>( + rect_node->data().background_brush.get()); + ColorRGBA color = solid_color_brush->color(); - SbBlitterSetColor(context_, RenderTreeToBlitterColor(color)); + if (render_state_.opacity < 1.0f) { + color.set_a(color.a() * render_state_.opacity); + } - SbBlitterFillRect(context_, RectFToBlitterRect(transformed_rect)); + SbBlitterSetBlending(context_, color.a() < 1.0f); + SbBlitterSetColor(context_, RenderTreeToBlitterColor(color)); + + SbBlitterFillRect(context_, RectFToBlitterRect(transformed_rect)); + } else if (background_brush_typeid == + base::GetTypeId<LinearGradientBrush>()) { + DCHECK(rect_node != NULL); + bool rendered_gradient = + RenderLinearGradient(device_, context_, render_state_, *rect_node); + if (!rendered_gradient) { + RenderWithSoftwareRenderer(rect_node); + return; + } + } else { + // If we have a more complicated brush fallback to software. + RenderWithSoftwareRenderer(rect_node); + return; + } } // Render the border, if it exists. @@ -370,103 +442,84 @@ float bottom_width = border.bottom.width * render_state_.transform.scale().y(); - RenderRectNodeBorder(context_, border.left.color, left_width, right_width, - top_width, bottom_width, transformed_rect); + ColorRGBA color = border.left.color; + if (render_state_.opacity < 1.0f) { + color.set_a(color.a() * render_state_.opacity); + } + RenderRectNodeBorder(context_, color, left_width, right_width, top_width, + bottom_width, transformed_rect); } } } void RenderTreeNodeVisitor::Visit( render_tree::RectShadowNode* rect_shadow_node) { + TRACE_EVENT0_IF_ENABLED("Visit(RectShadowNode)"); + RenderWithSoftwareRenderer(rect_shadow_node); } void RenderTreeNodeVisitor::Visit(render_tree::TextNode* text_node) { + TRACE_EVENT0_IF_ENABLED("Visit(TextNode)"); + RenderWithSoftwareRenderer(text_node); } void RenderTreeNodeVisitor::RenderWithSoftwareRenderer( render_tree::Node* node) { - common::SurfaceCache::Block cache_block(surface_cache_, node); - if (cache_block.Cached()) return; - - common::OffscreenRenderCoordinateMapping coord_mapping = - common::GetOffscreenRenderCoordinateMapping( - node->GetBounds(), render_state_.transform.ToMatrix(), - render_state_.bounds_stack.Top()); - if (coord_mapping.output_bounds.IsEmpty()) { - // There's nothing to render if the bounds are 0. + TRACE_EVENT0("cobalt::renderer", "RenderWithSoftwareRenderer()"); + CachedSoftwareRasterizer::SurfaceReference software_surface_reference( + software_surface_cache_, node, render_state_.transform); + CachedSoftwareRasterizer::Surface software_surface = + software_surface_reference.surface(); + if (!SbBlitterIsSurfaceValid(software_surface.surface)) { return; } - DCHECK_GE(0.001f, std::abs(1.0f - - render_state_.transform.scale().x() * - coord_mapping.output_post_scale.x())); - DCHECK_GE(0.001f, std::abs(1.0f - - render_state_.transform.scale().y() * - coord_mapping.output_post_scale.y())); + Transform apply_transform(render_state_.transform); + apply_transform.ApplyScale(software_surface.coord_mapping.output_post_scale); + math::RectF output_rectf = apply_transform.TransformRect( + software_surface.coord_mapping.output_bounds); + // We can simulate a "pre-multiply" by translation by offsetting the final + // output rectangle by the pre-translate, effectively resulting in the + // translation being applied last, as intended. + output_rectf.Offset(software_surface.coord_mapping.output_pre_translate); + SbBlitterRect output_blitter_rect = RectFToBlitterRect(output_rectf); - SkImageInfo output_image_info = SkImageInfo::MakeN32( - coord_mapping.output_bounds.width(), coord_mapping.output_bounds.height(), - kPremul_SkAlphaType); - - // Allocate the pixels for the output image. - SbBlitterPixelDataFormat blitter_pixel_data_format = - SkiaToBlitterPixelFormat(output_image_info.colorType()); - DCHECK(SbBlitterIsPixelFormatSupportedByPixelData(device_, - blitter_pixel_data_format)); - SbBlitterPixelData pixel_data = SbBlitterCreatePixelData( - device_, coord_mapping.output_bounds.width(), - coord_mapping.output_bounds.height(), blitter_pixel_data_format); - CHECK(SbBlitterIsPixelDataValid(pixel_data)); - - SkBitmap bitmap; - bitmap.installPixels(output_image_info, - SbBlitterGetPixelDataPointer(pixel_data), - SbBlitterGetPixelDataPitchInBytes(pixel_data)); - - // Setup our Skia canvas that we will be using as the target for all CPU Skia - // output. - SkCanvas canvas(bitmap); - canvas.clear(SkColorSetARGB(0, 0, 0, 0)); - - Transform sub_render_transform(coord_mapping.sub_render_transform); - - // Now setup our canvas so that the render tree will be rendered to the top - // left corner instead of at node->GetBounds().origin(). - canvas.translate(sub_render_transform.translate().x(), - sub_render_transform.translate().y()); - // And finally set the scale on our target canvas to match that of the current - // |render_state_.transform|. - canvas.scale(sub_render_transform.scale().x(), - sub_render_transform.scale().y()); - - // Use the Skia software rasterizer to render our subtree. - software_rasterizer_->Submit(node, &canvas); - - // Create a surface out of the now populated pixel data. - SbBlitterSurface surface = - SbBlitterCreateSurfaceFromPixelData(device_, pixel_data); - - math::RectF output_rect = coord_mapping.output_bounds; - output_rect.Offset(coord_mapping.output_pre_translate); - output_rect.Offset(render_state_.transform.translate()); - - // Finally blit the resulting surface to our actual render target. SbBlitterSetBlending(context_, true); - SbBlitterSetModulateBlitsWithColor(context_, false); - SbBlitterBlitRectToRect( - context_, surface, - SbBlitterMakeRect(0, 0, coord_mapping.output_bounds.width(), - coord_mapping.output_bounds.height()), - RectFToBlitterRect(output_rect)); - // Clean up our temporary surface. - SbBlitterDestroySurface(surface); + if (render_state_.opacity < 1.0f) { + SbBlitterSetModulateBlitsWithColor(context_, true); + SbBlitterSetColor( + context_, + SbBlitterColorFromRGBA(255, 255, 255, + static_cast<int>(255 * render_state_.opacity))); + } else { + SbBlitterSetModulateBlitsWithColor(context_, false); + } + +// Blit the software rasterized surface to our actual render target. +#if defined(ENABLE_DEBUG_CONSOLE) + if (render_state_.highlight_software_draws && software_surface.created) { + SbBlitterSetColor(context_, SbBlitterColorFromRGBA(0, 255, 0, 255)); + SbBlitterFillRect(context_, output_blitter_rect); + } else // NOLINT(readability/braces) +#endif // defined(ENABLE_DEBUG_CONSOLE) + { + TRACE_EVENT0("cobalt::renderer", "SbBlitterBlitRectToRect()"); + SbBlitterBlitRectToRect( + context_, software_surface.surface, + SbBlitterMakeRect( + 0, 0, software_surface.coord_mapping.output_bounds.width(), + software_surface.coord_mapping.output_bounds.height()), + output_blitter_rect); + } } scoped_ptr<RenderTreeNodeVisitor::OffscreenRender> RenderTreeNodeVisitor::RenderToOffscreenSurface(render_tree::Node* node) { + TRACE_EVENT0_IF_ENABLED("RenderToOffscreenSurface()"); + common::OffscreenRenderCoordinateMapping coord_mapping = common::GetOffscreenRenderCoordinateMapping( node->GetBounds(), render_state_.transform.ToMatrix(), @@ -496,8 +549,8 @@ RenderState( render_target, Transform(coord_mapping.sub_render_transform), BoundsStack(context_, Rect(coord_mapping.output_bounds.size()))), - software_rasterizer_, scratch_surface_cache_, surface_cache_delegate_, - surface_cache_); + scratch_surface_cache_, surface_cache_delegate_, surface_cache_, + software_surface_cache_); node->Accept(&sub_visitor); // Restore our original render target.
diff --git a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h index 4677308..938a5cf 100644 --- a/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h +++ b/src/cobalt/renderer/rasterizer/blitter/render_tree_node_visitor.h
@@ -32,13 +32,18 @@ #include "cobalt/render_tree/rect_node.h" #include "cobalt/render_tree/rect_shadow_node.h" #include "cobalt/render_tree/text_node.h" +#include "cobalt/renderer/rasterizer/blitter/cached_software_rasterizer.h" #include "cobalt/renderer/rasterizer/blitter/render_state.h" #include "cobalt/renderer/rasterizer/blitter/scratch_surface_cache.h" #include "cobalt/renderer/rasterizer/blitter/surface_cache_delegate.h" #include "cobalt/renderer/rasterizer/common/surface_cache.h" -#include "cobalt/renderer/rasterizer/skia/software_rasterizer.h" #include "starboard/blitter.h" +#include "third_party/skia/include/core/SkImageInfo.h" + +#ifndef SB_HAS_BLITTER +#define SB_HAS_BLITTER +#endif #if SB_HAS(BLITTER) @@ -62,10 +67,10 @@ public: RenderTreeNodeVisitor(SbBlitterDevice device, SbBlitterContext context, const RenderState& render_state, - skia::SoftwareRasterizer* software_rasterizer, ScratchSurfaceCache* scratch_surface_cache, SurfaceCacheDelegate* surface_cache_delegate, - common::SurfaceCache* surface_cache); + common::SurfaceCache* surface_cache, + CachedSoftwareRasterizer* software_surface_cache); void Visit(render_tree::animations::AnimateNode* animate_node) OVERRIDE { NOTREACHED(); @@ -99,10 +104,6 @@ }; scoped_ptr<OffscreenRender> RenderToOffscreenSurface(render_tree::Node* node); - // We maintain an instance of a software skia rasterizer which is used to - // render anything that we cannot render via the Blitter API directly. - skia::SoftwareRasterizer* software_rasterizer_; - SbBlitterDevice device_; SbBlitterContext context_; @@ -118,6 +119,10 @@ base::optional<SurfaceCacheDelegate::ScopedContext> surface_cache_scoped_context_; + // We fallback to software rasterization in order to render anything that we + // cannot render via the Blitter API directly. We cache the results. + CachedSoftwareRasterizer* software_surface_cache_; + DISALLOW_COPY_AND_ASSIGN(RenderTreeNodeVisitor); };
diff --git a/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc b/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc index 9c6a1e2..b23aa15 100644 --- a/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc +++ b/src/cobalt/renderer/rasterizer/blitter/resource_provider.cc
@@ -48,7 +48,8 @@ bool ResourceProvider::AlphaFormatSupported( render_tree::AlphaFormat alpha_format) { - return alpha_format == render_tree::kAlphaFormatPremultiplied; + return alpha_format == render_tree::kAlphaFormatPremultiplied || + alpha_format == render_tree::kAlphaFormatOpaque; } scoped_ptr<render_tree::ImageData> ResourceProvider::AllocateImageData( @@ -67,6 +68,31 @@ return make_scoped_refptr(new SinglePlaneImage(blitter_source_data.Pass())); } +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + +scoped_refptr<render_tree::Image> +ResourceProvider::CreateImageFromSbDecodeTarget(SbDecodeTarget decode_target) { + SbDecodeTargetFormat format = SbDecodeTargetGetFormat(decode_target); + if (format == kSbDecodeTargetFormat1PlaneRGBA) { + SbBlitterSurface surface = + SbDecodeTargetGetPlane(decode_target, kSbDecodeTargetPlaneRGBA); + DCHECK(SbBlitterIsSurfaceValid(surface)); + bool is_opaque = SbDecodeTargetIsOpaque(decode_target); + + // Now that we have the surface it contained, we are free to delete + // |decode_target|. + SbDecodeTargetDestroy(decode_target); + return make_scoped_refptr(new SinglePlaneImage(surface, is_opaque)); + } + + NOTREACHED() + << "Only format kSbDecodeTargetFormat1PlaneRGBA is currently supported."; + SbDecodeTargetDestroy(decode_target); + return NULL; +} + +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) + scoped_ptr<render_tree::RawImageMemory> ResourceProvider::AllocateRawImageMemory(size_t size_in_bytes, size_t alignment) { @@ -92,6 +118,13 @@ font_style); } +scoped_refptr<render_tree::Typeface> +ResourceProvider::GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) { + return skia_resource_provider_->GetLocalTypefaceByFaceNameIfAvailable( + font_face_name); +} + scoped_refptr<Typeface> ResourceProvider::GetCharacterFallbackTypeface( int32 utf32_character, FontStyle font_style, const std::string& language) { return skia_resource_provider_->GetCharacterFallbackTypeface(
diff --git a/src/cobalt/renderer/rasterizer/blitter/resource_provider.h b/src/cobalt/renderer/rasterizer/blitter/resource_provider.h index 078a683..c0ae7ab 100644 --- a/src/cobalt/renderer/rasterizer/blitter/resource_provider.h +++ b/src/cobalt/renderer/rasterizer/blitter/resource_provider.h
@@ -25,6 +25,7 @@ #include "cobalt/render_tree/image.h" #include "cobalt/render_tree/resource_provider.h" #include "starboard/blitter.h" +#include "starboard/decode_target.h" #if SB_HAS(BLITTER) @@ -42,6 +43,15 @@ void Finish() OVERRIDE {} +#if SB_VERSION(3) + scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget( + SbDecodeTarget decode_target) OVERRIDE; + + SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; } + + bool SupportsSbDecodeTarget() OVERRIDE { return true; } +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) + bool PixelFormatSupported(render_tree::PixelFormat pixel_format) OVERRIDE; bool AlphaFormatSupported(render_tree::AlphaFormat alpha_format) OVERRIDE; @@ -64,6 +74,9 @@ scoped_refptr<render_tree::Typeface> GetLocalTypeface( const char* font_family_name, render_tree::FontStyle font_style) OVERRIDE; + scoped_refptr<render_tree::Typeface> GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) OVERRIDE; + scoped_refptr<render_tree::Typeface> GetCharacterFallbackTypeface( int32 utf32_character, render_tree::FontStyle font_style, const std::string& language) OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.cc b/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.cc index cad2346..f96d931 100644 --- a/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.cc +++ b/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.cc
@@ -39,7 +39,8 @@ void SoftwareRasterizer::Submit( const scoped_refptr<render_tree::Node>& render_tree, - const scoped_refptr<backend::RenderTarget>& render_target, int options) { + const scoped_refptr<backend::RenderTarget>& render_target, + const Options& options) { int width = render_target->GetSize().width(); int height = render_target->GetSize().height(); @@ -94,7 +95,9 @@ SbBlitterDestroySurface(surface); + frame_rate_throttler_.EndInterval(); render_target_blitter->Flip(); + frame_rate_throttler_.BeginInterval(); } render_tree::ResourceProvider* SoftwareRasterizer::GetResourceProvider() {
diff --git a/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.h b/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.h index 4743332..04aa2ac 100644 --- a/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.h +++ b/src/cobalt/renderer/rasterizer/blitter/software_rasterizer.h
@@ -26,6 +26,7 @@ #include "cobalt/renderer/backend/blitter/graphics_context.h" #include "cobalt/renderer/backend/graphics_context.h" #include "cobalt/renderer/backend/render_target.h" +#include "cobalt/renderer/frame_rate_throttler.h" #include "cobalt/renderer/rasterizer/rasterizer.h" #include "cobalt/renderer/rasterizer/skia/software_rasterizer.h" @@ -44,13 +45,14 @@ void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target, - int options) OVERRIDE; + const Options& options) OVERRIDE; render_tree::ResourceProvider* GetResourceProvider() OVERRIDE; private: backend::GraphicsContextBlitter* context_; skia::SoftwareRasterizer skia_rasterizer_; + FrameRateThrottler frame_rate_throttler_; }; } // namespace blitter
diff --git a/src/cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h b/src/cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h index 4c114b1..c55f7cc 100644 --- a/src/cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h +++ b/src/cobalt/renderer/rasterizer/common/offscreen_render_coordinate_mapping.h
@@ -35,6 +35,9 @@ namespace common { struct OffscreenRenderCoordinateMapping { + OffscreenRenderCoordinateMapping() + : sub_render_transform(math::Matrix3F::Zeros()) {} + OffscreenRenderCoordinateMapping(const math::Rect& output_bounds, const math::Vector2dF& output_pre_translate, const math::Vector2dF& output_post_scale,
diff --git a/src/cobalt/renderer/rasterizer/egl/software_rasterizer.cc b/src/cobalt/renderer/rasterizer/egl/software_rasterizer.cc index e778d0f..8798b04 100644 --- a/src/cobalt/renderer/rasterizer/egl/software_rasterizer.cc +++ b/src/cobalt/renderer/rasterizer/egl/software_rasterizer.cc
@@ -40,7 +40,8 @@ void SoftwareRasterizer::Submit( const scoped_refptr<render_tree::Node>& render_tree, - const scoped_refptr<backend::RenderTarget>& render_target, int options) { + const scoped_refptr<backend::RenderTarget>& render_target, + const Options& options) { int width = render_target->GetSize().width(); int height = render_target->GetSize().height(); @@ -91,7 +92,9 @@ context_, render_target_egl); context_->Blit(output_texture->gl_handle(), 0, 0, width, height); + frame_rate_throttler_.EndInterval(); context_->SwapBuffers(render_target_egl); + frame_rate_throttler_.BeginInterval(); } render_tree::ResourceProvider* SoftwareRasterizer::GetResourceProvider() {
diff --git a/src/cobalt/renderer/rasterizer/egl/software_rasterizer.h b/src/cobalt/renderer/rasterizer/egl/software_rasterizer.h index 662353c..479ce7e 100644 --- a/src/cobalt/renderer/rasterizer/egl/software_rasterizer.h +++ b/src/cobalt/renderer/rasterizer/egl/software_rasterizer.h
@@ -21,6 +21,7 @@ #include "cobalt/render_tree/resource_provider.h" #include "cobalt/renderer/backend/graphics_context.h" #include "cobalt/renderer/backend/render_target.h" +#include "cobalt/renderer/frame_rate_throttler.h" #include "cobalt/renderer/rasterizer/rasterizer.h" #include "cobalt/renderer/rasterizer/skia/software_rasterizer.h" @@ -44,13 +45,14 @@ void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target, - int options) OVERRIDE; + const Options& options) OVERRIDE; render_tree::ResourceProvider* GetResourceProvider() OVERRIDE; private: backend::GraphicsContextEGL* context_; skia::SoftwareRasterizer skia_rasterizer_; + FrameRateThrottler frame_rate_throttler_; }; } // namespace egl
diff --git a/src/cobalt/renderer/rasterizer/pixel_test.cc b/src/cobalt/renderer/rasterizer/pixel_test.cc index 6f821ed..68e4c7a 100644 --- a/src/cobalt/renderer/rasterizer/pixel_test.cc +++ b/src/cobalt/renderer/rasterizer/pixel_test.cc
@@ -436,15 +436,21 @@ return resource_provider->CreateImage(image_data.Pass()); } -scoped_refptr<Image> CreateColoredCheckersImage( - ResourceProvider* resource_provider, const SizeF& dimensions) { +scoped_refptr<Image> CreateColoredCheckersImageForAlphaFormat( + ResourceProvider* resource_provider, const SizeF& dimensions, + render_tree::AlphaFormat alpha_format) { std::vector<RGBAWord> row_colors; row_colors.push_back(RGBAWord(255, 0, 0, 255)); row_colors.push_back(RGBAWord(0, 255, 0, 255)); row_colors.push_back(RGBAWord(0, 0, 255, 255)); return CreateAdditiveColorGridImage(resource_provider, dimensions, row_colors, - row_colors, - render_tree::kAlphaFormatPremultiplied); + row_colors, alpha_format); +} + +scoped_refptr<Image> CreateColoredCheckersImage( + ResourceProvider* resource_provider, const SizeF& dimensions) { + return CreateColoredCheckersImageForAlphaFormat( + resource_provider, dimensions, render_tree::kAlphaFormatPremultiplied); } } // namespace @@ -457,6 +463,53 @@ TestTree(new ImageNode(image)); } +TEST_F(PixelTest, SingleRGBAImageWithAlphaFormatOpaque) { + scoped_refptr<Image> image = CreateColoredCheckersImageForAlphaFormat( + GetResourceProvider(), output_surface_size(), + render_tree::kAlphaFormatOpaque); + + TestTree(new ImageNode(image)); +} + +TEST_F(PixelTest, SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners) { + scoped_refptr<Image> image = CreateColoredCheckersImageForAlphaFormat( + GetResourceProvider(), output_surface_size(), + render_tree::kAlphaFormatOpaque); + + TestTree(new FilterNode( + ViewportFilter(RectF(25, 25, 150, 150), RoundedCorners(75, 75)), + new ImageNode(image))); +} + +TEST_F(PixelTest, + SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor) { + scoped_refptr<Image> image = CreateColoredCheckersImageForAlphaFormat( + GetResourceProvider(), output_surface_size(), + render_tree::kAlphaFormatOpaque); + + CompositionNode::Builder builder; + builder.AddChild(new RectNode( + RectF(output_surface_size()), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(0.0, 1.0, 0.0, 1))))); + builder.AddChild(new FilterNode( + ViewportFilter(RectF(25, 25, 150, 150), RoundedCorners(75, 75)), + new ImageNode(image))); + TestTree(new CompositionNode(builder.Pass())); +} + +TEST_F(PixelTest, RectWithRoundedCornersOnSolidColor) { + CompositionNode::Builder builder; + builder.AddChild(new RectNode( + RectF(output_surface_size()), + scoped_ptr<Brush>(new SolidColorBrush(ColorRGBA(0.0, 1.0, 0.0, 1))))); + builder.AddChild(new FilterNode( + ViewportFilter(RectF(25, 25, 150, 150), RoundedCorners(75, 75)), + new RectNode(RectF(output_surface_size()), + scoped_ptr<Brush>( + new SolidColorBrush(ColorRGBA(0.0, 0.0, 1.0, 1)))))); + TestTree(new CompositionNode(builder.Pass())); +} + TEST_F(PixelTest, SingleRGBAImageLargerThanRenderTarget) { // Tests that rasterizing images that are larger than the render target // work as expected (e.g. they are cropped). @@ -2749,8 +2802,8 @@ RoundedCorners(50, 50))); } -// If SetBoundsCB() returns false, the PunchThroughVideoNode should have no -// effect. +// PunchThroughVideoNode should trigger the painting of a solid rectangle with +// RGBA(0, 0, 0, 0) regardless of whether SetBoundsCB returns true or false. TEST_F(PixelTest, PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse) { CompositionNode::Builder builder; builder.AddChild(new RectNode(RectF(25, 25, 150, 150), @@ -2763,8 +2816,6 @@ TestTree(new CompositionNode(builder.Pass())); } -// If SetBoundsCB() returns true, the PunchThroughVideoNode should trigger the -// painting of a solid rectangle with RGBA(0, 0, 0, 0). TEST_F(PixelTest, PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsTrue) { CompositionNode::Builder builder; builder.AddChild(new RectNode(RectF(25, 25, 150, 150),
diff --git a/src/cobalt/renderer/rasterizer/rasterizer.h b/src/cobalt/renderer/rasterizer/rasterizer.h index b15d4bb..fea7fe2 100644 --- a/src/cobalt/renderer/rasterizer/rasterizer.h +++ b/src/cobalt/renderer/rasterizer/rasterizer.h
@@ -18,6 +18,8 @@ #define COBALT_RENDERER_RASTERIZER_RASTERIZER_H_ #include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "cobalt/math/rect.h" #include "cobalt/render_tree/font.h" #include "cobalt/render_tree/image.h" #include "cobalt/render_tree/node.h" @@ -45,7 +47,21 @@ public: // When set, will clear the render target before rasterizing the render tree // to it. - static const int kSubmitOptions_Clear = (1 << 0); + static const int kSubmitFlags_Clear = (1 << 0); + + struct Options { + Options() : flags(0) {} + + // A bitwise combination of any of the |kSubmitFlags_*| constants defined + // above. + int flags; + + // If specified, indicates which region of |render_target| is + // dirty and needs to be updated. If animations are playing for example, + // then |dirty| can be setup to bound the animations. A rasterizer is free + // to ignore this value if they wish. + base::optional<math::Rect> dirty; + }; virtual ~Rasterizer() {} @@ -54,11 +70,11 @@ // above. virtual void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target, - int options) = 0; + const Options& options) = 0; void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target) { - Submit(render_tree, render_target, 0); + Submit(render_tree, render_target, Options()); } // Returns a thread-safe object from which one can produce renderer resources
diff --git a/src/cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.cc b/src/cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.cc index 1ca29c1..4b6e10c 100644 --- a/src/cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.cc +++ b/src/cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.cc
@@ -49,6 +49,8 @@ return kPremul_SkAlphaType; case render_tree::kAlphaFormatUnpremultiplied: return kUnpremul_SkAlphaType; + case render_tree::kAlphaFormatOpaque: + return kOpaque_SkAlphaType; default: DLOG(FATAL) << "Unknown render tree alpha format!"; } return kUnpremul_SkAlphaType;
diff --git a/src/cobalt/renderer/rasterizer/skia/conversions.h b/src/cobalt/renderer/rasterizer/skia/conversions.h new file mode 100644 index 0000000..c5640e4 --- /dev/null +++ b/src/cobalt/renderer/rasterizer/skia/conversions.h
@@ -0,0 +1,65 @@ +/* + * Copyright 2016 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_RENDERER_RASTERIZER_SKIA_CONVERSIONS_H_ +#define COBALT_RENDERER_RASTERIZER_SKIA_CONVERSIONS_H_ + +#include <vector> +#include "third_party/skia/include/core/SkColor.h" + +namespace cobalt { +namespace renderer { +namespace rasterizer { +namespace skia { + +inline SkColor ToSkColor(const render_tree::ColorRGBA& color) { + return SkColorSetARGB(color.rgb8_a(), color.rgb8_r(), color.rgb8_g(), + color.rgb8_b()); +} + +// Helper struct to convert render_tree::ColorStopList to a format that Skia +// methods will easily accept. +struct SkiaColorStops { + explicit SkiaColorStops(const render_tree::ColorStopList& color_stops) + : colors(color_stops.size()), + positions(color_stops.size()), + has_alpha(false) { + for (size_t i = 0; i < color_stops.size(); ++i) { + if (color_stops[i].color.a() < 1.0f) { + has_alpha = true; + } + + colors[i] = ToSkColor(color_stops[i].color); + positions[i] = color_stops[i].position; + } + } + + std::size_t size() const { + DCHECK(colors.size() == positions.size()); + return colors.size(); + } + + std::vector<SkColor> colors; + std::vector<SkScalar> positions; + bool has_alpha; +}; + +} // namespace skia +} // namespace rasterizer +} // namespace renderer +} // namespace cobalt + +#endif // COBALT_RENDERER_RASTERIZER_SKIA_CONVERSIONS_H_
diff --git a/src/cobalt/renderer/rasterizer/skia/font.cc b/src/cobalt/renderer/rasterizer/skia/font.cc index 06bbb20..7f357b9 100644 --- a/src/cobalt/renderer/rasterizer/skia/font.cc +++ b/src/cobalt/renderer/rasterizer/skia/font.cc
@@ -117,7 +117,8 @@ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); SkRect skia_bounds; - float width = paint.measureText(&glyph, 2, &skia_bounds); + float width = + paint.measureText(&glyph, sizeof(render_tree::GlyphIndex), &skia_bounds); // Both cache and return the glyph's bounds. if (glyph < kPrimaryPageSize) {
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc index f8096b0..2c8db59 100644 --- a/src/cobalt/renderer/rasterizer/skia/hardware_image.cc +++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.cc
@@ -164,7 +164,9 @@ scoped_ptr<HardwareImageData> image_data, backend::GraphicsContextEGL* cobalt_context, GrContext* gr_context, MessageLoop* rasterizer_message_loop) - : size_(image_data->GetDescriptor().size), + : is_opaque_(image_data->GetDescriptor().alpha_format == + render_tree::kAlphaFormatOpaque), + size_(image_data->GetDescriptor().size), rasterizer_message_loop_(rasterizer_message_loop) { TRACE_EVENT0("cobalt::renderer", "HardwareFrontendImage::HardwareFrontendImage()"); @@ -180,7 +182,8 @@ intptr_t offset, const render_tree::ImageDataDescriptor& descriptor, backend::GraphicsContextEGL* cobalt_context, GrContext* gr_context, MessageLoop* rasterizer_message_loop) - : size_(descriptor.size), + : is_opaque_(descriptor.alpha_format == render_tree::kAlphaFormatOpaque), + size_(descriptor.size), rasterizer_message_loop_(rasterizer_message_loop) { TRACE_EVENT0("cobalt::renderer", "HardwareFrontendImage::HardwareFrontendImage()"); @@ -210,12 +213,14 @@ return backend_image_->GetBitmap(); } -void HardwareFrontendImage::EnsureInitialized() { +bool HardwareFrontendImage::EnsureInitialized() { DCHECK_EQ(rasterizer_message_loop_, MessageLoop::current()); if (!initialize_backend_image_.is_null()) { initialize_backend_image_.Run(); initialize_backend_image_.Reset(); + return true; } + return false; } void HardwareFrontendImage::InitializeBackendImageFromImageData( @@ -257,16 +262,18 @@ HardwareMultiPlaneImage::~HardwareMultiPlaneImage() {} -void HardwareMultiPlaneImage::EnsureInitialized() { +bool HardwareMultiPlaneImage::EnsureInitialized() { // A multi-plane image is not considered backend-initialized until all its // single-plane images are backend-initialized, thus we ensure that all // the component images are backend-initialized. + bool initialized = false; for (int i = 0; i < render_tree::MultiPlaneImageDataDescriptor::kMaxPlanes; ++i) { if (planes_[i]) { - planes_[i]->EnsureInitialized(); + initialized |= planes_[i]->EnsureInitialized(); } } + return initialized; } } // namespace skia
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_image.h b/src/cobalt/renderer/rasterizer/skia/hardware_image.h index 15c318e..bb44b1a 100644 --- a/src/cobalt/renderer/rasterizer/skia/hardware_image.h +++ b/src/cobalt/renderer/rasterizer/skia/hardware_image.h
@@ -100,7 +100,9 @@ // restraint should always be satisfied naturally. const SkBitmap& GetBitmap() const OVERRIDE; - void EnsureInitialized() OVERRIDE; + bool EnsureInitialized() OVERRIDE; + + bool IsOpaque() const OVERRIDE { return is_opaque_; } private: ~HardwareFrontendImage() OVERRIDE; @@ -116,6 +118,10 @@ intptr_t offset, const render_tree::ImageDataDescriptor& descriptor, backend::GraphicsContextEGL* cobalt_context, GrContext* gr_context); + // Track if we have any alpha or not, which can enable optimizations in the + // case that alpha is not present. + bool is_opaque_; + // We shadow the image dimensions so they can be quickly looked up from just // the frontend image object. const math::Size size_; @@ -164,7 +170,7 @@ return planes_[plane_index]->GetBitmap(); } - void EnsureInitialized() OVERRIDE; + bool EnsureInitialized() OVERRIDE; private: ~HardwareMultiPlaneImage() OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc index 20ddfd5..2ea8e6e 100644 --- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc +++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.cc
@@ -20,6 +20,8 @@ #include "base/debug/trace_event.h" #include "cobalt/renderer/backend/egl/graphics_context.h" +#include "cobalt/renderer/backend/egl/graphics_system.h" +#include "cobalt/renderer/frame_rate_throttler.h" #include "cobalt/renderer/rasterizer/common/surface_cache.h" #include "cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.h" #include "cobalt/renderer/rasterizer/skia/hardware_resource_provider.h" @@ -47,7 +49,7 @@ void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target, - int options); + const Options& options); render_tree::ResourceProvider* GetResourceProvider(); @@ -70,6 +72,8 @@ scoped_ptr<RenderTreeNodeVisitor::ScratchSurface> CreateScratchSurface( const math::Size& size); + void ResetSkiaState(); + base::ThreadChecker thread_checker_; backend::GraphicsContextEGL* graphics_context_; @@ -83,6 +87,8 @@ base::optional<SurfaceCacheDelegate> surface_cache_delegate_; base::optional<common::SurfaceCache> surface_cache_; + + FrameRateThrottler frame_rate_throttler_; }; namespace { @@ -189,7 +195,8 @@ void HardwareRasterizer::Impl::Submit( const scoped_refptr<render_tree::Node>& render_tree, - const scoped_refptr<backend::RenderTarget>& render_target, int options) { + const scoped_refptr<backend::RenderTarget>& render_target, + const Options& options) { DCHECK(thread_checker_.CalledOnValidThread()); backend::RenderTargetEGL* render_target_egl = @@ -224,9 +231,17 @@ // Get a SkCanvas that outputs to our hardware render target. SkCanvas* canvas = sk_output_surface_->getCanvas(); + canvas->save(); - if (options & Rasterizer::kSubmitOptions_Clear) { + if (options.flags & Rasterizer::kSubmitFlags_Clear) { canvas->clear(SkColorSetARGB(0, 0, 0, 0)); + } else if (options.dirty) { + // Only a portion of the display is dirty. Reuse the previous frame + // if possible. + if (render_target_egl->IsContentPreservedOnSwap() && + render_target_egl->swap_count() >= 3) { + canvas->clipRect(CobaltRectFToSkiaRect(*options.dirty)); + } } { @@ -238,6 +253,8 @@ base::Unretained(this)); RenderTreeNodeVisitor visitor( canvas, &create_scratch_surface_function, + base::Bind(&HardwareRasterizer::Impl::ResetSkiaState, + base::Unretained(this)), surface_cache_delegate_ ? &surface_cache_delegate_.value() : NULL, surface_cache_ ? &surface_cache_.value() : NULL); render_tree->Accept(&visitor); @@ -248,7 +265,10 @@ canvas->flush(); } + frame_rate_throttler_.EndInterval(); graphics_context_->SwapBuffers(render_target_egl); + frame_rate_throttler_.BeginInterval(); + canvas->restore(); } render_tree::ResourceProvider* HardwareRasterizer::Impl::GetResourceProvider() { @@ -304,6 +324,8 @@ } } +void HardwareRasterizer::Impl::ResetSkiaState() { gr_context_->resetContext(); } + HardwareRasterizer::HardwareRasterizer( backend::GraphicsContext* graphics_context, int skia_cache_size_in_bytes, int scratch_surface_cache_size_in_bytes, int surface_cache_size_in_bytes) @@ -315,7 +337,8 @@ void HardwareRasterizer::Submit( const scoped_refptr<render_tree::Node>& render_tree, - const scoped_refptr<backend::RenderTarget>& render_target, int options) { + const scoped_refptr<backend::RenderTarget>& render_target, + const Options& options) { TRACE_EVENT0("cobalt::renderer", "Rasterizer::Submit()"); TRACE_EVENT0("cobalt::renderer", "HardwareRasterizer::Submit()"); impl_->Submit(render_tree, render_target, options);
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h index 8807fc3..af2b78e 100644 --- a/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h +++ b/src/cobalt/renderer/rasterizer/skia/hardware_rasterizer.h
@@ -59,7 +59,7 @@ // into the constructor. void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target, - int options) OVERRIDE; + const Options& options) OVERRIDE; render_tree::ResourceProvider* GetResourceProvider() OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc index d79a244..195b6fb 100644 --- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc +++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.cc
@@ -25,6 +25,7 @@ #include "cobalt/renderer/rasterizer/skia/gl_format_conversions.h" #include "cobalt/renderer/rasterizer/skia/glyph_buffer.h" #include "cobalt/renderer/rasterizer/skia/hardware_image.h" +#include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h" #include "cobalt/renderer/rasterizer/skia/typeface.h" #include "third_party/ots/include/opentype-sanitiser.h" #include "third_party/ots/include/ots-memory-stream.h" @@ -67,7 +68,8 @@ bool HardwareResourceProvider::AlphaFormatSupported( render_tree::AlphaFormat alpha_format) { - return alpha_format == render_tree::kAlphaFormatPremultiplied; + return alpha_format == render_tree::kAlphaFormatPremultiplied || + alpha_format == render_tree::kAlphaFormatOpaque; } scoped_ptr<ImageData> HardwareResourceProvider::AllocateImageData( @@ -95,7 +97,8 @@ const render_tree::ImageDataDescriptor& descriptor = skia_hardware_source_data->GetDescriptor(); - DCHECK_EQ(render_tree::kAlphaFormatPremultiplied, descriptor.alpha_format); + DCHECK(descriptor.alpha_format == render_tree::kAlphaFormatPremultiplied || + descriptor.alpha_format == render_tree::kAlphaFormatOpaque); #if defined(COBALT_BUILD_TYPE_DEBUG) Image::DCheckForPremultipliedAlpha(descriptor.size, descriptor.pitch_in_bytes, descriptor.pixel_format, @@ -166,6 +169,24 @@ } scoped_refptr<render_tree::Typeface> +HardwareResourceProvider::GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) { + TRACE_EVENT0("cobalt::renderer", + "HardwareResourceProvider::GetLocalTypefaceIfAvailable()"); + + SkFontMgr_Cobalt* font_manager = + base::polymorphic_downcast<SkFontMgr_Cobalt*>(font_manager_.get()); + + SkTypeface* typeface = font_manager->matchFaceName(font_face_name); + if (typeface != NULL) { + SkAutoTUnref<SkTypeface> typeface_unref_helper(typeface); + return scoped_refptr<render_tree::Typeface>(new SkiaTypeface(typeface)); + } + + return NULL; +} + +scoped_refptr<render_tree::Typeface> HardwareResourceProvider::GetCharacterFallbackTypeface( int32 character, render_tree::FontStyle font_style, const std::string& language) {
diff --git a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h index 54a439d..1d18280 100644 --- a/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h +++ b/src/cobalt/renderer/rasterizer/skia/hardware_resource_provider.h
@@ -51,6 +51,27 @@ scoped_refptr<render_tree::Image> CreateImage( scoped_ptr<render_tree::ImageData> pixel_data) OVERRIDE; +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + + scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget( + SbDecodeTarget decode_target) OVERRIDE { + NOTREACHED() + << "CreateImageFromSbDecodeTarget is not supported on EGL yet."; + SbDecodeTargetDestroy(decode_target); + return NULL; + } + + // Return the associated SbDecodeTargetProvider with the ResourceProvider, + // if it exists. Returns NULL if SbDecodeTarget is not supported. + SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; } + + // Whether SbDecodeTargetIsSupported or not. + bool SupportsSbDecodeTarget() OVERRIDE { return false; } + +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) +#endif // defined(STARBOARD) + scoped_ptr<render_tree::RawImageMemory> AllocateRawImageMemory( size_t size_in_bytes, size_t alignment) OVERRIDE; @@ -63,6 +84,9 @@ scoped_refptr<render_tree::Typeface> GetLocalTypeface( const char* font_family_name, render_tree::FontStyle font_style) OVERRIDE; + scoped_refptr<render_tree::Typeface> GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) OVERRIDE; + scoped_refptr<render_tree::Typeface> GetCharacterFallbackTypeface( int32 character, render_tree::FontStyle font_style, const std::string& language) OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/skia/image.h b/src/cobalt/renderer/rasterizer/skia/image.h index cd2e907..da4a5dc 100644 --- a/src/cobalt/renderer/rasterizer/skia/image.h +++ b/src/cobalt/renderer/rasterizer/skia/image.h
@@ -41,7 +41,8 @@ // to be referenced in a render tree before then, and thus this function will // fast-track the initialization of the backend object if it has not yet // executed. - virtual void EnsureInitialized() = 0; + // Returns true if an initialization actually took place. + virtual bool EnsureInitialized() = 0; // Mechanism to allow dynamic type checking on Image objects. virtual base::TypeId GetTypeId() const = 0; @@ -78,6 +79,9 @@ base::TypeId GetTypeId() const OVERRIDE { return base::GetTypeId<MultiPlaneImage>(); } + + // All currently supported MultiPlaneImage formats do not feature alpha. + bool IsOpaque() const OVERRIDE { return true; } }; } // namespace skia
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc index ab9fc7d..fd5dfd9 100644 --- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc +++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.cc
@@ -72,13 +72,15 @@ RenderTreeNodeVisitor::RenderTreeNodeVisitor( SkCanvas* render_target, const CreateScratchSurfaceFunction* create_scratch_surface_function, + const base::Closure& reset_skia_context_function, SurfaceCacheDelegate* surface_cache_delegate, common::SurfaceCache* surface_cache, Type visitor_type) : draw_state_(render_target), create_scratch_surface_function_(create_scratch_surface_function), surface_cache_delegate_(surface_cache_delegate), surface_cache_(surface_cache), - visitor_type_(visitor_type) { + visitor_type_(visitor_type), + reset_skia_context_function_(reset_skia_context_function) { DCHECK_EQ(surface_cache_delegate_ == NULL, surface_cache_ == NULL); if (surface_cache_delegate_) { // Update our surface cache delegate to point to this render tree node @@ -187,7 +189,7 @@ } void ApplyViewportMask( - SkCanvas* canvas, + RenderTreeNodeVisitorDrawState* draw_state, const base::optional<render_tree::ViewportFilter>& filter) { if (!filter) { return; @@ -195,11 +197,12 @@ if (!filter->has_rounded_corners()) { SkRect filter_viewport(CobaltRectFToSkiaRect(filter->viewport())); - canvas->clipRect(filter_viewport); + draw_state->render_target->clipRect(filter_viewport); } else { - canvas->clipPath( + draw_state->render_target->clipPath( RoundedRectToSkiaPath(filter->viewport(), filter->rounded_corners()), SkRegion::kIntersect_Op, true /* doAntiAlias */); + draw_state->clip_is_rect = false; } } @@ -264,9 +267,9 @@ // Render our source sub-tree into the offscreen surface. { - RenderTreeNodeVisitor sub_visitor(canvas, create_scratch_surface_function_, - surface_cache_delegate_, surface_cache_, - kType_SubVisitor); + RenderTreeNodeVisitor sub_visitor( + canvas, create_scratch_surface_function_, reset_skia_context_function_, + surface_cache_delegate_, surface_cache_, kType_SubVisitor); filter_node.source->Accept(&sub_visitor); } @@ -285,7 +288,7 @@ // the offscreen surface, so reset the scale for now. draw_state_.render_target->save(); ApplyBlurFilterToPaint(&paint, filter_node.blur_filter); - ApplyViewportMask(draw_state_.render_target, filter_node.viewport_filter); + ApplyViewportMask(&draw_state_, filter_node.viewport_filter); draw_state_.render_target->setMatrix(CobaltMatrixToSkia( math::TranslateMatrix(coord_mapping.output_pre_translate) * total_matrix * @@ -364,7 +367,22 @@ void RenderTreeNodeVisitor::Visit(render_tree::FilterNode* filter_node) { if (filter_node->data().map_to_mesh_filter) { - // TODO: Implement support for MapToMeshFilter. + // TODO: Implement support for MapToMeshFilter instead of punching out + // the area that it occupies. + SkPaint paint; + paint.setXfermodeMode(SkXfermode::kSrc_Mode); + paint.setARGB(0, 0, 0, 0); + + math::RectF bounds = filter_node->GetBounds(); + SkRect sk_rect = SkRect::MakeXYWH(bounds.x(), bounds.y(), bounds.width(), + bounds.height()); + + draw_state_.render_target->drawRect(sk_rect, paint); + +#if ENABLE_FLUSH_AFTER_EVERY_NODE + draw_state_.render_target->flush(); +#endif + return; } @@ -435,13 +453,17 @@ RenderTreeNodeVisitorDrawState original_draw_state(draw_state_); draw_state_.render_target->save(); - ApplyViewportMask(draw_state_.render_target, - filter_node->data().viewport_filter); + // Remember the value of |clip_is_rect| because ApplyViewportMask may + // modify it. + bool clip_was_rect = draw_state_.clip_is_rect; + ApplyViewportMask(&draw_state_, filter_node->data().viewport_filter); if (filter_node->data().opacity_filter) { draw_state_.opacity *= filter_node->data().opacity_filter->opacity(); } filter_node->data().source->Accept(this); + + draw_state_.clip_is_rect = clip_was_rect; draw_state_.render_target->restore(); draw_state_ = original_draw_state; } else { @@ -488,12 +510,14 @@ } SkPaint CreateSkPaintForImageRendering( - const RenderTreeNodeVisitorDrawState& draw_state) { + const RenderTreeNodeVisitorDrawState& draw_state, bool is_opaque) { SkPaint paint; paint.setFilterLevel(SkPaint::kLow_FilterLevel); if (draw_state.opacity < 1.0f) { paint.setAlpha(draw_state.opacity * 255); + } else if (is_opaque && draw_state.clip_is_rect) { + paint.setXfermodeMode(SkXfermode::kSrc_Mode); } return paint; @@ -503,7 +527,8 @@ RenderTreeNodeVisitorDrawState* draw_state, const math::RectF& destination_rect, const math::Matrix3F* local_transform) { - SkPaint paint = CreateSkPaintForImageRendering(*draw_state); + SkPaint paint = CreateSkPaintForImageRendering( + *draw_state, single_plane_image->IsOpaque()); // In the most frequent by far case where the normalized transformed image // texture coordinates lie within the unit square, then we must ensure NOT @@ -591,7 +616,8 @@ } } - SkPaint paint = CreateSkPaintForImageRendering(*draw_state); + SkPaint paint = CreateSkPaintForImageRendering(*draw_state, + multi_plane_image->IsOpaque()); paint.setShader(yuv2rgb_shader); draw_state->render_target->drawRect(CobaltRectFToSkiaRect(destination_rect), paint); @@ -622,7 +648,14 @@ // This should be a quick operation, and it needs to happen eventually, so // if it is not done now, it will be done in the next frame, or the one after // that. - image->EnsureInitialized(); + if (image->EnsureInitialized()) { + // EnsureInitialized() may make a number of GL calls that results in GL + // state being modified behind Skia's back, therefore we have Skia reset its + // state in this case. + if (!reset_skia_context_function_.is_null()) { + reset_skia_context_function_.Run(); + } + } // We issue different skia rasterization commands to render the image // depending on whether it's single or multi planed. @@ -704,18 +737,11 @@ SkRect sk_rect_transformed; total_matrix.mapRect(&sk_rect_transformed, sk_rect); - if (punch_through_video_node->data().set_bounds_cb.is_null()) { - return; - } - bool render_punch_through = - punch_through_video_node->data().set_bounds_cb.Run( - math::Rect(static_cast<int>(sk_rect_transformed.x()), - static_cast<int>(sk_rect_transformed.y()), - static_cast<int>(sk_rect_transformed.width()), - static_cast<int>(sk_rect_transformed.height()))); - if (!render_punch_through) { - return; - } + punch_through_video_node->data().set_bounds_cb.Run( + math::Rect(static_cast<int>(sk_rect_transformed.x()), + static_cast<int>(sk_rect_transformed.y()), + static_cast<int>(sk_rect_transformed.width()), + static_cast<int>(sk_rect_transformed.height()))); SkPaint paint; paint.setXfermodeMode(SkXfermode::kSrc_Mode);
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h index 3efa3c6..65dac0e 100644 --- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h +++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor.h
@@ -73,6 +73,7 @@ RenderTreeNodeVisitor( SkCanvas* render_target, const CreateScratchSurfaceFunction* create_scratch_surface_function, + const base::Closure& reset_skia_context_function, SurfaceCacheDelegate* surface_cache_delegate, common::SurfaceCache* surface_cache, Type visitor_type = kType_Normal); @@ -104,6 +105,8 @@ base::optional<SurfaceCacheDelegate::ScopedContext> surface_cache_scoped_context_; + base::Closure reset_skia_context_function_; + DISALLOW_COPY_AND_ASSIGN(RenderTreeNodeVisitor); };
diff --git a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor_draw_state.h b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor_draw_state.h index 01a5d62..d3cb0fe 100644 --- a/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor_draw_state.h +++ b/src/cobalt/renderer/rasterizer/skia/render_tree_node_visitor_draw_state.h
@@ -26,10 +26,14 @@ struct RenderTreeNodeVisitorDrawState { explicit RenderTreeNodeVisitorDrawState(SkCanvas* render_target) - : render_target(render_target), opacity(1.0f) {} + : render_target(render_target), opacity(1.0f), clip_is_rect(true) {} SkCanvas* render_target; float opacity; + + // True if the current clip is a rectangle or not. If it is not, we need + // to enable blending when rendering clipped rectangles. + bool clip_is_rect; }; } // namespace skia
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/skia_library.gypi b/src/cobalt/renderer/rasterizer/skia/skia/skia_library.gypi index 49aee75..dba75b6 100644 --- a/src/cobalt/renderer/rasterizer/skia/skia/skia_library.gypi +++ b/src/cobalt/renderer/rasterizer/skia/skia/skia_library.gypi
@@ -151,6 +151,9 @@ '<(DEPTH)/third_party/skia/src/fonts/SkGScalerContext.h', '<(DEPTH)/third_party/skia/src/fonts/SkTestScalerContext.cpp', '<(DEPTH)/third_party/skia/src/fonts/SkTestScalerContext.h', + + # Conflicts with cobalt implementation. + '<(DEPTH)/third_party/skia/src/gpu/gl/GrGLCreateNativeInterface_none.cpp', ], # Exclude Skia OpenGL backend source files.
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc index 013d0dc..4111f1b 100644 --- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc +++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontConfigParser_cobalt.cc
@@ -250,6 +250,7 @@ const char* name = attributes[i]; const char* value = attributes[i + 1]; size_t name_len = strlen(name); + if (name_len == 4 && strncmp(name, "name", name_len) == 0) { SkAutoAsciiToLC to_lowercase(value); family->names.push_back().set(to_lowercase.lc()); @@ -268,25 +269,79 @@ } void FontElementHandler(FontFileInfo* file, const char** attributes) { - // A <font> should have weight (integer) and style (normal, italic)attributes. + DCHECK(file != NULL); + + // A <font> should have following attributes: + // weight (integer), style (normal, italic), font_name (string), and + // postscript_name (string). // The element should contain a filename. + + enum SeenAttributeFlags { + kSeenNone = 0, + kSeenFontFullName = 1, + kSeenFontPostscriptName = 1 << 1, + kSeenWeight = 1 << 2, + kSeenStyle = 1 << 3 + }; + + uint32_t seen_attributes_flag = kSeenNone; for (size_t i = 0; attributes[i] != NULL && attributes[i + 1] != NULL; i += 2) { const char* name = attributes[i]; const char* value = attributes[i + 1]; - size_t name_len = strlen(name); - if (name_len == 6 && strncmp("weight", name, name_len) == 0) { - if (!ParseNonNegativeInteger(value, &file->weight)) { - SkDebugf("---- Font weight %s (INVALID)", value); - file->weight = 0; - } - } else if (name_len == 5 && strncmp("style", name, name_len) == 0) { - size_t value_len = strlen(value); - if (value_len == 6 && strncmp("italic", value, value_len) == 0) { - file->style = FontFileInfo::kItalic_FontStyle; - } + + switch (strlen(name)) { + case 9: + if (strncmp("font_name", name, 9) == 0) { + file->full_font_name = value; + seen_attributes_flag |= kSeenFontFullName; + continue; + } + break; + case 15: + if (strncmp("postscript_name", name, 15) == 0) { + file->postscript_name = value; + seen_attributes_flag |= kSeenFontPostscriptName; + continue; + } + break; + case 6: + if (strncmp("weight", name, 6) == 0) { + if (!ParseNonNegativeInteger(value, &file->weight)) { + DLOG(WARNING) << "Invalid font weight [" << value << "]"; + file->weight = 0; + } else { + seen_attributes_flag |= kSeenWeight; + } + continue; + } + break; + case 5: + if (strncmp("style", name, 5) == 0) { + if (strncmp("italic", value, 6) == 0) { + file->style = FontFileInfo::kItalic_FontStyle; + seen_attributes_flag |= kSeenStyle; + continue; + } else if (strncmp("normal", value, 6) == 0) { + file->style = FontFileInfo::kNormal_FontStyle; + seen_attributes_flag |= kSeenStyle; + continue; + } else { + NOTREACHED() << "Unsupported style [" << value << "]"; + } + } + break; + default: + break; } + + NOTREACHED() << "Unsupported attribute [" << name << "]"; } + + DCHECK_EQ(seen_attributes_flag, kSeenFontFullName | kSeenFontPostscriptName | + kSeenWeight | kSeenStyle); + DCHECK(!file->full_font_name.isEmpty()); + DCHECK(!file->postscript_name.isEmpty()); } FontFamily* FindFamily(FamilyData* family_data, const char* family_name) {
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc index 21b6b4c..6c9aa9d 100644 --- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc +++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.cc
@@ -16,11 +16,16 @@ #include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h" +#if defined(STARBOARD) +#include "starboard/file.h" +#else #include <sys/stat.h> +#endif #include <cmath> #include "base/at_exit.h" #include "base/bind.h" +#include "base/debug/trace_event.h" #include "base/lazy_instance.h" #include "base/memory/singleton.h" #include "cobalt/base/c_val.h" @@ -32,6 +37,7 @@ #include "SkString.h" #include "SkTArray.h" #include "SkTSearch.h" +#include "third_party/skia/src/ports/SkFontHost_FreeType_common.h" /////////////////////////////////////////////////////////////////////////////// @@ -43,6 +49,8 @@ // NOTE: It is the responsibility of the caller to call Unref() on the SkData. SkData* NewDataFromFile(const SkString& file_path) { + TRACE_EVENT1("cobalt::renderer", "SkFontMgr_cobalt::NewDataFromFile()", + "file_path", TRACE_STR_COPY(file_path.c_str())); LOG(INFO) << "Loading font file: " << file_path.c_str(); SkAutoTUnref<SkStream> file_stream(SkStream::NewFromFile(file_path.c_str())); @@ -154,6 +162,7 @@ } virtual SkStream* onOpenStream(int* ttc_index) const SK_OVERRIDE { + TRACE_EVENT0("cobalt::renderer", "SkTypeface_CobaltSystem::onOpenStream()"); *ttc_index = index_; // Scope the initial mutex lock. @@ -170,6 +179,7 @@ } } + TRACE_EVENT0("cobalt::renderer", "Load data from file"); // This is where the bulk of the time will be spent, so load the font data // outside of a mutex lock. SkAutoTUnref<SkData> data(NewDataFromFile(path_name_)); @@ -232,6 +242,8 @@ language_(family.language), page_ranges_(family.page_ranges), is_character_map_generated_(false) { + TRACE_EVENT0("cobalt::renderer", + "SkFontStyleSet_Cobalt::SkFontStyleSet_Cobalt()"); DCHECK(manager_owned_mutex_); if (family.names.count() == 0) { @@ -246,8 +258,13 @@ SkString path_name(SkOSPath::Join(base_path, font_file.file_name.c_str())); // Sanity check that something exists at this location. +#if defined(STARBOARD) + bool is_font_file_found = SbFileExists(path_name.c_str()); +#else struct stat status; - if (0 != stat(path_name.c_str(), &status)) { + bool is_font_file_found = (stat(path_name.c_str(), &status) == 0); +#endif // defined(STARBOARD) + if (!is_font_file_found) { LOG(ERROR) << "Failed to find font file: " << path_name.c_str(); continue; } @@ -257,8 +274,14 @@ ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant); - styles_.push_back().reset(SkNEW_ARGS(SkFontStyleSetEntry_Cobalt, - (path_name, font_file.index, style))); + std::string full_font_name(font_file.full_font_name.c_str(), + font_file.full_font_name.size()); + std::string postscript_name(font_file.postscript_name.c_str(), + font_file.postscript_name.size()); + + styles_.push_back().reset(SkNEW_ARGS( + SkFontStyleSetEntry_Cobalt, + (path_name, font_file.index, style, full_font_name, postscript_name))); } } @@ -290,34 +313,57 @@ SkTypeface* SkFontStyleSet_Cobalt::MatchStyleWithoutLocking( const SkFontStyle& pattern) { - while (styles_.count() > 0) { - int style_index = GetClosestStyleIndex(pattern); - SkFontStyleSetEntry_Cobalt* style = styles_[style_index]; - if (style->typeface == NULL) { - CreateSystemTypeface(style); - - // Check to see if the typeface is still NULL. If this is the case, then - // the style can't be created. Remove it from the array. - if (style->typeface == NULL) { - styles_[style_index].swap(&styles_.back()); - styles_.pop_back(); - continue; - } - } - - return SkRef(style->typeface.get()); + SkTypeface* typeface = NULL; + while (typeface == NULL && styles_.count() > 0) { + typeface = TryRetrieveTypefaceAndRemoveStyleOnFailure( + GetClosestStyleIndex(pattern)); } + return typeface; +} +SkTypeface* SkFontStyleSet_Cobalt::MatchFullFontName(const std::string& name) { + for (int i = 0; i < styles_.count(); ++i) { + if (styles_[i]->full_font_name == name) { + return TryRetrieveTypefaceAndRemoveStyleOnFailure(i); + } + } return NULL; } +SkTypeface* SkFontStyleSet_Cobalt::MatchFontPostScriptName( + const std::string& name) { + for (int i = 0; i < styles_.count(); ++i) { + if (styles_[i]->font_postscript_name == name) { + return TryRetrieveTypefaceAndRemoveStyleOnFailure(i); + } + } + return NULL; +} + +SkTypeface* SkFontStyleSet_Cobalt::TryRetrieveTypefaceAndRemoveStyleOnFailure( + int style_index) { + DCHECK(style_index >= 0 && style_index < styles_.count()); + SkFontStyleSetEntry_Cobalt* style = styles_[style_index]; + // If the typeface doesn't already exist, then attempt to create it. + if (style->typeface == NULL) { + CreateSystemTypeface(style); + // If the creation attempt failed and the typeface is still NULL, then + // remove the entry from the set's styles. + if (style->typeface == NULL) { + styles_[style_index].swap(&styles_.back()); + styles_.pop_back(); + return NULL; + } + } + return SkRef(style->typeface.get()); +} + bool SkFontStyleSet_Cobalt::ContainsTypeface(const SkTypeface* typeface) { for (int i = 0; i < styles_.count(); ++i) { if (styles_[i]->typeface == typeface) { return true; } } - return false; } @@ -355,6 +401,9 @@ // The character map is lazily generated. Generate it now if it isn't already // generated. if (!is_character_map_generated_) { + TRACE_EVENT0("cobalt::renderer", + "SkFontStyleSet_Cobalt::ContainsCharacter() and " + "!is_character_map_generated_"); // Attempt to load the closest font style from the set. If it fails to load, // it will be removed from the set and, as long as font styles remain in the // set, the logic will be attempted again. @@ -406,6 +455,7 @@ if (is_character_map_generated_) { return; } + TRACE_EVENT0("cobalt::renderer", "GenerateCharacterMapFromData()"); FT_Library freetype_lib; if (FT_Init_FreeType(&freetype_lib) != 0) { @@ -460,6 +510,8 @@ void SkFontStyleSet_Cobalt::CreateSystemTypeface( SkFontStyleSetEntry_Cobalt* style_entry) { + TRACE_EVENT0("cobalt::renderer", + "SkFontStyleSet_Cobalt::CreateSystemTypeface()"); SkAutoTUnref<SkData> font_data(NewDataFromFile(style_entry->font_file_path)); if (font_data != NULL) { CreateSystemTypefaceFromData(style_entry, font_data); @@ -468,6 +520,8 @@ void SkFontStyleSet_Cobalt::CreateSystemTypefaceFromData( SkFontStyleSetEntry_Cobalt* style_entry, SkData* data) { + TRACE_EVENT0("cobalt::renderer", + "SkFontStyleSet_Cobalt::CreateSystemTypefaceFromData()"); DCHECK(!style_entry->typeface); // Since the font data is available, generate the character map if this is a @@ -566,13 +620,17 @@ const char* directory, const SkTArray<SkString, true>& default_fonts) : default_family_(NULL), last_font_cache_purge_time_(base::TimeTicks::Now()) { + TRACE_EVENT0("cobalt::renderer", "SkFontMgr_Cobalt::SkFontMgr_Cobalt()"); // Ensure that both the CValManager and SkFontMgrCVals are initialized. The // CValManager is created first as it must outlast the SkFontMgrCVals. base::CValManager::GetInstance(); SkFontMgrCVals::GetInstance(); SkTDArray<FontFamily*> font_families; - SkFontConfigParser::GetFontFamilies(directory, &font_families); + { + TRACE_EVENT0("cobalt::renderer", "SkFontConfigParser::GetFontFamilies()"); + SkFontConfigParser::GetFontFamilies(directory, &font_families); + } BuildNameToFamilyMap(directory, &font_families); font_families.deleteAll(); @@ -594,6 +652,41 @@ } } +SkTypeface* SkFontMgr_Cobalt::matchFaceName(const std::string& font_face_name) { + if (font_face_name.empty()) { + return NULL; + } + + // Lock the style sets mutex prior to accessing them. + SkAutoMutexAcquire scoped_mutex(style_sets_mutex_); + + // Prioritize looking up the postscript name first since some of our client + // applications prefer this method to specify face names. + for (int i = 0; i <= 1; ++i) { + NameToStyleSetMap& name_to_style_set_map = + i == 0 ? font_postscript_name_to_style_set_map_ + : full_font_name_to_style_set_map_; + + NameToStyleSetMap::iterator style_set_iterator = + name_to_style_set_map.find(font_face_name); + if (style_set_iterator != name_to_style_set_map.end()) { + SkFontStyleSet_Cobalt* style_set = style_set_iterator->second; + SkTypeface* typeface = + i == 0 ? style_set->MatchFontPostScriptName(font_face_name) + : style_set->MatchFullFontName(font_face_name); + if (typeface != NULL) { + return typeface; + } else { + // If no typeface was successfully created then remove the entry from + // the map. It won't provide a successful result in subsequent calls + // either. + name_to_style_set_map.erase(style_set_iterator); + } + } + } + return NULL; +} + int SkFontMgr_Cobalt::onCountFamilies() const { return family_names_.count(); } void SkFontMgr_Cobalt::onGetFamilyName(int index, SkString* family_name) const { @@ -610,7 +703,7 @@ return NULL; } - NameToFamilyMap::const_iterator family_iterator = + NameToStyleSetMap::const_iterator family_iterator = name_to_family_map_.find(family_names_[index].c_str()); if (family_iterator != name_to_family_map_.end()) { return SkRef(family_iterator->second); @@ -627,7 +720,7 @@ SkAutoAsciiToLC tolc(family_name); - NameToFamilyMap::const_iterator family_iterator = + NameToStyleSetMap::const_iterator family_iterator = name_to_family_map_.find(tolc.lc()); if (family_iterator != name_to_family_map_.end()) { return SkRef(family_iterator->second); @@ -718,6 +811,7 @@ SkTypeface* SkFontMgr_Cobalt::onCreateFromStream(SkStream* stream, int ttc_index) const { + TRACE_EVENT0("cobalt::renderer", "SkFontMgr_Cobalt::onCreateFromStream()"); bool is_fixed_pitch; SkTypeface::Style style; SkString name; @@ -731,6 +825,7 @@ SkTypeface* SkFontMgr_Cobalt::onCreateFromFile(const char path[], int ttc_index) const { + TRACE_EVENT0("cobalt::renderer", "SkFontMgr_Cobalt::onCreateFromFile()"); SkAutoTUnref<SkStream> stream(SkStream::NewFromFile(path)); return stream.get() ? createFromStream(stream, ttc_index) : NULL; } @@ -749,6 +844,7 @@ void SkFontMgr_Cobalt::BuildNameToFamilyMap(const char* base_path, SkTDArray<FontFamily*>* families) { + TRACE_EVENT0("cobalt::renderer", "SkFontMgr_Cobalt::BuildNameToFamilyMap()"); for (int i = 0; i < families->count(); i++) { FontFamily& family = *(*families)[i]; bool named_font = family.names.count() > 0; @@ -768,6 +864,39 @@ continue; } + for (SkAutoTUnref<SkFontStyleSet_Cobalt::SkFontStyleSetEntry_Cobalt>* + font_style_set_entry = new_set->styles_.begin(); + font_style_set_entry != new_set->styles_.end(); + ++font_style_set_entry) { + // On the first pass through, process the full font name. + // On the second pass through, process the font postscript name. + for (int i = 0; i <= 1; ++i) { + const std::string font_face_name_type_description = + i == 0 ? "Full Font" : "Postscript"; + const std::string& font_face_name = + i == 0 ? (*font_style_set_entry)->full_font_name + : (*font_style_set_entry)->font_postscript_name; + NameToStyleSetMap& font_face_name_style_set_map = + i == 0 ? full_font_name_to_style_set_map_ + : font_postscript_name_to_style_set_map_; + + DCHECK(!font_face_name.empty()); + if (font_face_name_style_set_map.find(font_face_name) == + font_face_name_style_set_map.end()) { + DLOG(INFO) << "Adding " << font_face_name_type_description + << " name [" << font_face_name << "]."; + font_face_name_style_set_map[font_face_name] = new_set.get(); + } else { + // Purposely, not overwriting the entry gives priority to the + // earlier entry. This is consistent with how fonts.xml gives + // priority to fonts that are specified earlier in the file. + NOTREACHED() << font_face_name_type_description << " name [" + << font_face_name + << "] already registered in BuildNameToFamilyMap."; + } + } + } + font_style_sets_.push_back().reset(SkRef(new_set.get())); if (named_font) { @@ -851,6 +980,29 @@ return NULL; } +void SkFontMgr_Cobalt::HandlePeriodicProcessing() { + base::TimeTicks current_time = base::TimeTicks::Now(); + + ProcessSystemTypefacesWithOpenStreams(current_time); + + // If the required delay has elapsed since the last font cache purge, then + // it's time to force another. This is accomplished by setting the limit to + // 1 byte smaller than it's current size, which initiates a partial purge (it + // always purges at least 25% of its contents). After this is done, the cache + // is set back to its previous size. + if ((current_time - last_font_cache_purge_time_).InMilliseconds() >= + kPeriodicFontCachePurgeDelayMs) { + last_font_cache_purge_time_ = current_time; + + size_t font_cache_used = SkGraphics::GetFontCacheUsed(); + if (font_cache_used > 0) { + size_t font_cache_limit = SkGraphics::GetFontCacheLimit(); + SkGraphics::SetFontCacheLimit(font_cache_used - 1); + SkGraphics::SetFontCacheLimit(font_cache_limit); + } + } +} + // NOTE: It is the responsibility of the caller to lock // |system_typeface_stream_mutex_| void SkFontMgr_Cobalt::AddSystemTypefaceWithActiveOpenStream( @@ -887,26 +1039,6 @@ } } -void SkFontMgr_Cobalt::HandlePeriodicProcessing() { - base::TimeTicks current_time = base::TimeTicks::Now(); - - ProcessSystemTypefacesWithOpenStreams(current_time); - - // If the required delay has elapsed since the last font cache purge, then - // it's time to force another. This is accomplished by setting the limit to - // 1 byte smaller than it's current size, which initiates a partial purge (it - // always purges at least 25% of its contents). After this is done, the cache - // is set back to its previous size. - if ((current_time - last_font_cache_purge_time_).InMilliseconds() >= - kPeriodicFontCachePurgeDelayMs) { - last_font_cache_purge_time_ = current_time; - - size_t font_cache_limit = SkGraphics::GetFontCacheLimit(); - SkGraphics::SetFontCacheLimit(SkGraphics::GetFontCacheUsed() - 1); - SkGraphics::SetFontCacheLimit(font_cache_limit); - } -} - void SkFontMgr_Cobalt::ProcessSystemTypefacesWithOpenStreams( const base::TimeTicks& current_time) const { SkAutoMutexAcquire scoped_mutex(system_typeface_stream_mutex_);
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h index e09b727..cc74881 100644 --- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h +++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h
@@ -26,7 +26,6 @@ #include "cobalt/base/poller.h" #include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h" #include "SkFontHost.h" -#include "SkFontHost_FreeType_common.h" #include "SkFontDescriptor.h" #include "SkFontMgr.h" #include "SkThread.h" @@ -47,16 +46,31 @@ class SkFontStyleSet_Cobalt : public SkFontStyleSet { public: struct SkFontStyleSetEntry_Cobalt : public SkRefCnt { + // NOTE: |SkFontStyleSetEntry_Cobalt| objects are not guaranteed to last for + // the lifetime of |SkFontMgr_Cobalt| and can be removed by their owning + // |SkFontStyleSet_Cobalt| if their typeface fails to load properly. As a + // result, it is not safe to store their pointers outside of + // |SkFontStyleSet_Cobalt|. SkFontStyleSetEntry_Cobalt(const SkString& file_path, const int index, - const SkFontStyle& style) + const SkFontStyle& style, + const std::string& full_name, + const std::string& postscript_name) : font_file_path(file_path), ttc_index(index), font_style(style), + full_font_name(full_name.data(), full_name.size()), + font_postscript_name(postscript_name.data(), postscript_name.size()), typeface(NULL) {} const SkString font_file_path; const int ttc_index; const SkFontStyle font_style; + + // these two members are declared as std::string since we have a hasher + // for std::string, but not SkString at this point. + const std::string full_font_name; + const std::string font_postscript_name; + SkAutoTUnref<SkTypeface> typeface; }; @@ -83,6 +97,9 @@ // calling any of the non-const private functions. SkTypeface* MatchStyleWithoutLocking(const SkFontStyle& pattern); + SkTypeface* MatchFullFontName(const std::string& name); + SkTypeface* MatchFontPostScriptName(const std::string& name); + SkTypeface* TryRetrieveTypefaceAndRemoveStyleOnFailure(int style_index); bool ContainsTypeface(const SkTypeface* typeface); bool ContainsCharacter(const SkFontStyle& style, SkUnichar character); @@ -129,6 +146,9 @@ SkFontMgr_Cobalt(const char* directory, const SkTArray<SkString, true>& default_fonts); + // NOTE: This returns NULL if a match is not found. + SkTypeface* matchFaceName(const std::string& font_face_name); + protected: // From SkFontMgr virtual int onCountFamilies() const SK_OVERRIDE; @@ -136,14 +156,20 @@ virtual void onGetFamilyName(int index, SkString* family_name) const SK_OVERRIDE; + // NOTE: This returns NULL if there is no accessible style set at the index. virtual SkFontStyleSet_Cobalt* onCreateStyleSet(int index) const SK_OVERRIDE; + // NOTE: This returns NULL if there is no family match. virtual SkFontStyleSet_Cobalt* onMatchFamily(const char family_name[]) const SK_OVERRIDE; + // NOTE: This always returns a non-NULL value. If the family name cannot be + // found, then the best match among the default family is returned. virtual SkTypeface* onMatchFamilyStyle( const char family_name[], const SkFontStyle& style) const SK_OVERRIDE; +// NOTE: This always returns a non-NULL value. If no match can be found, then +// the best match among the default family is returned. #ifdef SK_FM_NEW_MATCH_FAMILY_STYLE_CHARACTER virtual SkTypeface* onMatchFamilyStyleCharacter( const char family_name[], const SkFontStyle& style, const char* bcp47[], @@ -154,19 +180,25 @@ SkUnichar character) const SK_OVERRIDE; #endif + // NOTE: This returns NULL if a match is not found. virtual SkTypeface* onMatchFaceStyle(const SkTypeface* family_member, const SkFontStyle& font_style) const SK_OVERRIDE; + // NOTE: This returns NULL if the typeface cannot be created. virtual SkTypeface* onCreateFromData(SkData* data, int ttc_index) const SK_OVERRIDE; + // NOTE: This returns NULL if the typeface cannot be created. virtual SkTypeface* onCreateFromStream(SkStream* stream, int ttc_index) const SK_OVERRIDE; + // NOTE: This returns NULL if the typeface cannot be created. virtual SkTypeface* onCreateFromFile(const char path[], int ttc_index) const SK_OVERRIDE; + // NOTE: This always returns a non-NULL value. If no match can be found, then + // the best match among the default family is returned. virtual SkTypeface* onLegacyCreateTypeface( const char family_name[], unsigned style_bits) const SK_OVERRIDE; @@ -180,9 +212,7 @@ base::TimeTicks time; }; - // Map names to the back end so that all names for a given family refer to - // the same (non-replicated) set of typefaces. - typedef base::hash_map<std::string, SkFontStyleSet_Cobalt*> NameToFamilyMap; + typedef base::hash_map<std::string, SkFontStyleSet_Cobalt*> NameToStyleSetMap; void BuildNameToFamilyMap(const char* base_path, SkTDArray<FontFamily*>* families); @@ -222,7 +252,11 @@ SkTArray<SkAutoTUnref<SkFontStyleSet_Cobalt>, true> font_style_sets_; SkTArray<SkString> family_names_; - NameToFamilyMap name_to_family_map_; + // Map names to the back end so that all names for a given family refer to + // the same (non-replicated) set of typefaces. + NameToStyleSetMap name_to_family_map_; + NameToStyleSetMap full_font_name_to_style_set_map_; + NameToStyleSetMap font_postscript_name_to_style_set_map_; SkTArray<SkFontStyleSet_Cobalt*> fallback_families_;
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h index 5e12828..80287a4 100644 --- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h +++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontUtil_cobalt.h
@@ -25,7 +25,6 @@ #include "SkString.h" #include "SkTDArray.h" - // The font_character_map namespace contains all of the constants, typedefs, and // utility functions used with determining whether or not a font supports a // character. @@ -110,6 +109,9 @@ int index; int weight; FontStyle style; + + SkString full_font_name; + SkString postscript_name; }; // A font family provides one or more names for a collection of fonts, each of
diff --git a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkMemory_starboard.cc b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkMemory_starboard.cc index 8d843b7..531621f 100644 --- a/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkMemory_starboard.cc +++ b/src/cobalt/renderer/rasterizer/skia/skia/src/ports/SkMemory_starboard.cc
@@ -54,7 +54,7 @@ void sk_free(void* p) { if (p) { - SbMemoryFree(p); + SbMemoryDeallocate(p); } }
diff --git a/src/cobalt/renderer/rasterizer/skia/software_image.cc b/src/cobalt/renderer/rasterizer/skia/software_image.cc index 5f68cde..9c05a86 100644 --- a/src/cobalt/renderer/rasterizer/skia/software_image.cc +++ b/src/cobalt/renderer/rasterizer/skia/software_image.cc
@@ -79,6 +79,7 @@ void SoftwareImage::Initialize( uint8_t* source_data, const render_tree::ImageDataDescriptor& descriptor) { + is_opaque_ = (descriptor.alpha_format == render_tree::kAlphaFormatOpaque); SkAlphaType skia_alpha_format = RenderTreeAlphaFormatToSkia(descriptor.alpha_format); DCHECK_EQ(kPremul_SkAlphaType, skia_alpha_format);
diff --git a/src/cobalt/renderer/rasterizer/skia/software_image.h b/src/cobalt/renderer/rasterizer/skia/software_image.h index 37bd1ad..d01f212 100644 --- a/src/cobalt/renderer/rasterizer/skia/software_image.h +++ b/src/cobalt/renderer/rasterizer/skia/software_image.h
@@ -55,7 +55,9 @@ const SkBitmap& GetBitmap() const OVERRIDE { return bitmap_; } - void EnsureInitialized() OVERRIDE {} + bool EnsureInitialized() OVERRIDE { return false; } + + bool IsOpaque() const OVERRIDE { return is_opaque_; } private: void Initialize(uint8_t* source_data, @@ -64,6 +66,7 @@ scoped_array<uint8_t> owned_pixel_data_; SkBitmap bitmap_; math::Size size_; + bool is_opaque_; }; class SoftwareRawImageMemory : public render_tree::RawImageMemory { @@ -95,7 +98,11 @@ return planes_[plane_index]->GetBitmap(); } - void EnsureInitialized() OVERRIDE {} + bool EnsureInitialized() OVERRIDE { return false; } + + // Currently, all supported multiplane images (e.g. mostly YUV) do not + // support alpha, so multiplane images will always be opaque. + bool IsOpaque() const OVERRIDE { return true; } private: math::Size size_;
diff --git a/src/cobalt/renderer/rasterizer/skia/software_rasterizer.cc b/src/cobalt/renderer/rasterizer/skia/software_rasterizer.cc index d1cafc6..884db9e 100644 --- a/src/cobalt/renderer/rasterizer/skia/software_rasterizer.cc +++ b/src/cobalt/renderer/rasterizer/skia/software_rasterizer.cc
@@ -120,7 +120,7 @@ RenderTreeNodeVisitor::CreateScratchSurfaceFunction create_scratch_surface_function = base::Bind(&CreateScratchSurface); RenderTreeNodeVisitor visitor( - render_target, &create_scratch_surface_function, + render_target, &create_scratch_surface_function, base::Closure(), surface_cache_delegate_ ? &surface_cache_delegate_.value() : NULL, surface_cache_ ? &surface_cache_.value() : NULL);
diff --git a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc index 04ca7c1..5a9afb7 100644 --- a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc +++ b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.cc
@@ -21,6 +21,7 @@ #include "cobalt/renderer/rasterizer/skia/cobalt_skia_type_conversions.h" #include "cobalt/renderer/rasterizer/skia/font.h" #include "cobalt/renderer/rasterizer/skia/glyph_buffer.h" +#include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontMgr_cobalt.h" #include "cobalt/renderer/rasterizer/skia/software_image.h" #include "cobalt/renderer/rasterizer/skia/typeface.h" #include "third_party/ots/include/opentype-sanitiser.h" @@ -46,7 +47,8 @@ bool SoftwareResourceProvider::AlphaFormatSupported( render_tree::AlphaFormat alpha_format) { - return alpha_format == render_tree::kAlphaFormatPremultiplied; + return alpha_format == render_tree::kAlphaFormatPremultiplied || + alpha_format == render_tree::kAlphaFormatOpaque; } scoped_ptr<ImageData> SoftwareResourceProvider::AllocateImageData( @@ -108,7 +110,8 @@ scoped_refptr<render_tree::Typeface> SoftwareResourceProvider::GetLocalTypeface( const char* font_family_name, render_tree::FontStyle font_style) { - TRACE_EVENT0("cobalt::renderer", "SoftwareResourceProvider::GetLocalFont()"); + TRACE_EVENT0("cobalt::renderer", + "SoftwareResourceProvider::GetLocalTypeface()"); SkAutoTUnref<SkTypeface> typeface(font_manager_->matchFamilyStyle( font_family_name, CobaltFontStyleToSkFontStyle(font_style))); @@ -116,6 +119,24 @@ } scoped_refptr<render_tree::Typeface> +SoftwareResourceProvider::GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) { + TRACE_EVENT0("cobalt::renderer", + "SoftwareResourceProvider::GetLocalTypefaceIfAvailable()"); + + SkFontMgr_Cobalt* font_manager = + base::polymorphic_downcast<SkFontMgr_Cobalt*>(font_manager_.get()); + + SkTypeface* typeface = font_manager->matchFaceName(font_face_name); + if (typeface != NULL) { + SkAutoTUnref<SkTypeface> typeface_unref_helper(typeface); + return scoped_refptr<render_tree::Typeface>(new SkiaTypeface(typeface)); + } + + return NULL; +} + +scoped_refptr<render_tree::Typeface> SoftwareResourceProvider::GetCharacterFallbackTypeface( int32 character, render_tree::FontStyle font_style, const std::string& language) {
diff --git a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h index 7be9105..9528e22 100644 --- a/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h +++ b/src/cobalt/renderer/rasterizer/skia/software_resource_provider.h
@@ -46,6 +46,21 @@ scoped_refptr<render_tree::Image> CreateImage( scoped_ptr<render_tree::ImageData> pixel_data) OVERRIDE; +#if defined(STARBOARD) +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + scoped_refptr<render_tree::Image> CreateImageFromSbDecodeTarget( + SbDecodeTarget decode_target) OVERRIDE { + NOTREACHED(); + SbDecodeTargetDestroy(decode_target); + return NULL; + } + + SbDecodeTargetProvider* GetSbDecodeTargetProvider() OVERRIDE { return NULL; } + + bool SupportsSbDecodeTarget() OVERRIDE { return false; } +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS) +#endif // defined(STARBOARD) + scoped_ptr<render_tree::RawImageMemory> AllocateRawImageMemory( size_t size_in_bytes, size_t alignment) OVERRIDE; @@ -58,6 +73,9 @@ scoped_refptr<render_tree::Typeface> GetLocalTypeface( const char* font_family_name, render_tree::FontStyle font_style) OVERRIDE; + scoped_refptr<render_tree::Typeface> GetLocalTypefaceByFaceNameIfAvailable( + const std::string& font_face_name) OVERRIDE; + scoped_refptr<render_tree::Typeface> GetCharacterFallbackTypeface( int32 character, render_tree::FontStyle font_style, const std::string& language) OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/stub/rasterizer.h b/src/cobalt/renderer/rasterizer/stub/rasterizer.h index 2c4f5bf..ff69528 100644 --- a/src/cobalt/renderer/rasterizer/stub/rasterizer.h +++ b/src/cobalt/renderer/rasterizer/stub/rasterizer.h
@@ -31,7 +31,7 @@ void Submit(const scoped_refptr<render_tree::Node>& render_tree, const scoped_refptr<backend::RenderTarget>& render_target, - int options) OVERRIDE {} + const Options& options) OVERRIDE {} render_tree::ResourceProvider* GetResourceProvider() OVERRIDE;
diff --git a/src/cobalt/renderer/rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png b/src/cobalt/renderer/rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png index d26f087..0f82d7a 100644 --- a/src/cobalt/renderer/rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png +++ b/src/cobalt/renderer/rasterizer/testdata/PunchThroughVideoNodePunchesThroughSetBoundsCBReturnsFalse-expected.png Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/RectWithRoundedCornersOnSolidColor-expected.png b/src/cobalt/renderer/rasterizer/testdata/RectWithRoundedCornersOnSolidColor-expected.png new file mode 100644 index 0000000..e72e8d1 --- /dev/null +++ b/src/cobalt/renderer/rasterizer/testdata/RectWithRoundedCornersOnSolidColor-expected.png Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaque-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaque-expected.png new file mode 100644 index 0000000..b8638eb --- /dev/null +++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaque-expected.png Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners-expected.png new file mode 100644 index 0000000..2076087 --- /dev/null +++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCorners-expected.png Binary files differ
diff --git a/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor-expected.png b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor-expected.png new file mode 100644 index 0000000..6251d5f --- /dev/null +++ b/src/cobalt/renderer/rasterizer/testdata/SingleRGBAImageWithAlphaFormatOpaqueAndRoundedCornersOnSolidColor-expected.png Binary files differ
diff --git a/src/cobalt/renderer/render_tree_pixel_tester.cc b/src/cobalt/renderer/render_tree_pixel_tester.cc index 6e6fa75..717ab06 100644 --- a/src/cobalt/renderer/render_tree_pixel_tester.cc +++ b/src/cobalt/renderer/render_tree_pixel_tester.cc
@@ -311,8 +311,9 @@ scoped_array<uint8_t> RenderTreePixelTester::RasterizeRenderTree( const scoped_refptr<cobalt::render_tree::Node>& tree) const { // Rasterize the test render tree to the rasterizer's offscreen render target. - rasterizer_->Submit(tree, test_surface_, - rasterizer::Rasterizer::kSubmitOptions_Clear); + rasterizer::Rasterizer::Options rasterizer_options; + rasterizer_options.flags = rasterizer::Rasterizer::kSubmitFlags_Clear; + rasterizer_->Submit(tree, test_surface_, rasterizer_options); // Load the texture's pixel data into a CPU memory buffer and return it. return graphics_context_->DownloadPixelDataAsRGBA(test_surface_);
diff --git a/src/cobalt/renderer/renderer.gyp b/src/cobalt/renderer/renderer.gyp index 5a1253e..d7619d5 100644 --- a/src/cobalt/renderer/renderer.gyp +++ b/src/cobalt/renderer/renderer.gyp
@@ -22,6 +22,8 @@ 'target_name': 'renderer', 'type': 'static_library', 'sources': [ + 'frame_rate_throttler.cc', + 'frame_rate_throttler.h', 'pipeline.cc', 'pipeline.h', 'renderer_module.cc', @@ -33,6 +35,9 @@ 'submission_queue.h', ], + 'defines': [ + 'COBALT_MINIMUM_FRAME_TIME_IN_MILLISECONDS=<(cobalt_minimum_frame_time_in_milliseconds)', + ], 'includes': [ 'copy_font_data.gypi', ],
diff --git a/src/cobalt/renderer/renderer_module.cc b/src/cobalt/renderer/renderer_module.cc index 49cded6..23ee70e 100644 --- a/src/cobalt/renderer/renderer_module.cc +++ b/src/cobalt/renderer/renderer_module.cc
@@ -79,7 +79,8 @@ pipeline_ = make_scoped_ptr(new renderer::Pipeline( base::Bind(options_.create_rasterizer_function, graphics_context_.get(), options_), - display_->GetRenderTarget(), graphics_context_.get())); + display_->GetRenderTarget(), graphics_context_.get(), + options_.submit_even_if_render_tree_is_unchanged)); } }
diff --git a/src/cobalt/renderer/renderer_module.h b/src/cobalt/renderer/renderer_module.h index a6fe61d..96226ca 100644 --- a/src/cobalt/renderer/renderer_module.h +++ b/src/cobalt/renderer/renderer_module.h
@@ -55,11 +55,23 @@ // using the hardware-accelerated Skia rasterizer. int skia_cache_size_in_bytes; + // Only relevant if you are using the Blitter API. + // Determines the capacity of the software surface cache, which is used to + // cache all surfaces that are rendered via a software rasterizer to avoid + // re-rendering them. + int software_surface_cache_size_in_bytes; + // Determines the capacity of the surface cache. The surface cache tracks // which render tree nodes are being re-used across frames and stores the // nodes that are most CPU-expensive to render into surfaces. int surface_cache_size_in_bytes; + // If this flag is set to true, the pipeline will not re-submit a render + // tree if it has not changed from the previous submission. This can save + // CPU time so long as there's no problem with the fact that the display + // buffer will not be frequently swapped. + bool submit_even_if_render_tree_is_unchanged; + private: // Implemented per-platform, and allows each platform to customize // the renderer options.
diff --git a/src/cobalt/renderer/renderer_module_default_options_starboard.cc b/src/cobalt/renderer/renderer_module_default_options_starboard.cc index db5086b..c19cc0b 100644 --- a/src/cobalt/renderer/renderer_module_default_options_starboard.cc +++ b/src/cobalt/renderer/renderer_module_default_options_starboard.cc
@@ -16,6 +16,7 @@ #include "cobalt/renderer/renderer_module.h" +#include "base/debug/trace_event.h" #include "cobalt/renderer/rasterizer/blitter/hardware_rasterizer.h" #include "cobalt/renderer/rasterizer/blitter/software_rasterizer.h" #include "cobalt/renderer/rasterizer/egl/software_rasterizer.h" @@ -30,6 +31,7 @@ scoped_ptr<rasterizer::Rasterizer> CreateRasterizer( backend::GraphicsContext* graphics_context, const RendererModule::Options& options) { + TRACE_EVENT0("cobalt::renderer", "CreateRasterizer"); #if COBALT_FORCE_STUB_RASTERIZER return scoped_ptr<rasterizer::Rasterizer>(new rasterizer::stub::Rasterizer()); #else @@ -54,7 +56,8 @@ return scoped_ptr<rasterizer::Rasterizer>( new rasterizer::blitter::HardwareRasterizer( graphics_context, options.scratch_surface_cache_size_in_bytes, - options.surface_cache_size_in_bytes)); + options.surface_cache_size_in_bytes, + options.software_surface_cache_size_in_bytes)); #endif // COBALT_FORCE_SOFTWARE_RASTERIZER #else #error "Either GLES2 or the Starboard Blitter API must be available." @@ -70,6 +73,14 @@ skia_cache_size_in_bytes = COBALT_SKIA_CACHE_SIZE_IN_BYTES; scratch_surface_cache_size_in_bytes = COBALT_SCRATCH_SURFACE_CACHE_SIZE_IN_BYTES; + software_surface_cache_size_in_bytes = + COBALT_SOFTWARE_SURFACE_CACHE_SIZE_IN_BYTES; + + // If there is no need to frequently flip the display buffer, then enable + // support for an optimization where the scene is not re-rasterized each frame + // if it has not changed from the last frame. + submit_even_if_render_tree_is_unchanged = + SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER; create_rasterizer_function = base::Bind(&CreateRasterizer); }
diff --git a/src/cobalt/renderer/renderer_module_default_options_win.cc b/src/cobalt/renderer/renderer_module_default_options_win.cc index 6b08fc9..b96c5f0 100644 --- a/src/cobalt/renderer/renderer_module_default_options_win.cc +++ b/src/cobalt/renderer/renderer_module_default_options_win.cc
@@ -47,6 +47,8 @@ scratch_surface_cache_size_in_bytes = COBALT_SCRATCH_SURFACE_CACHE_SIZE_IN_BYTES; + submit_even_if_render_tree_is_unchanged = true; + create_rasterizer_function = base::Bind(&CreateRasterizer); }
diff --git a/src/cobalt/renderer/renderer_parameters_setup.gypi b/src/cobalt/renderer/renderer_parameters_setup.gypi index 95724f6..ed0313b 100644 --- a/src/cobalt/renderer/renderer_parameters_setup.gypi +++ b/src/cobalt/renderer/renderer_parameters_setup.gypi
@@ -17,6 +17,7 @@ 'COBALT_SKIA_CACHE_SIZE_IN_BYTES=<(skia_cache_size_in_bytes)', 'COBALT_SCRATCH_SURFACE_CACHE_SIZE_IN_BYTES=<(scratch_surface_cache_size_in_bytes)', 'COBALT_SURFACE_CACHE_SIZE_IN_BYTES=<(surface_cache_size_in_bytes)', + 'COBALT_SOFTWARE_SURFACE_CACHE_SIZE_IN_BYTES=<(software_surface_cache_size_in_bytes)', ], 'conditions': [ ['rasterizer_type == "software"', {
diff --git a/src/cobalt/renderer/test/png_utils/png_decode.cc b/src/cobalt/renderer/test/png_utils/png_decode.cc index 4bf217b..b65618b 100644 --- a/src/cobalt/renderer/test/png_utils/png_decode.cc +++ b/src/cobalt/renderer/test/png_utils/png_decode.cc
@@ -209,6 +209,9 @@ } } } break; + case render_tree::kAlphaFormatOpaque: { + NOTREACHED() << "kAlphaFormatOpaque not supported."; + } break; } // End transformations. Get the updated info, and then verify.
diff --git a/src/cobalt/script/mozjs/conversion_helpers.h b/src/cobalt/script/mozjs/conversion_helpers.h index f5eca1e..6166bfa 100644 --- a/src/cobalt/script/mozjs/conversion_helpers.h +++ b/src/cobalt/script/mozjs/conversion_helpers.h
@@ -71,7 +71,6 @@ size_t length = in_string.length(); jschar* inflated_buffer = js::InflateUTF8String(context, in_string.c_str(), &length); - DCHECK(inflated_buffer); if (!inflated_buffer) { LOG(ERROR) << "Failed to inflate UTF8 string.";
diff --git a/src/cobalt/script/mozjs/mozjs.gyp b/src/cobalt/script/mozjs/mozjs.gyp index 6e856f5..7e833e4 100644 --- a/src/cobalt/script/mozjs/mozjs.gyp +++ b/src/cobalt/script/mozjs/mozjs.gyp
@@ -26,6 +26,7 @@ 'mozjs_global_environment.cc', 'mozjs_property_enumerator.cc', 'mozjs_source_code.cc', + 'mozjs_trace_logging.cc', 'opaque_root_tracker.cc', 'proxy_handler.cc', 'referenced_object_map.cc',
diff --git a/src/cobalt/script/mozjs/mozjs_callback_function.h b/src/cobalt/script/mozjs/mozjs_callback_function.h index a2714de..ef6932a 100644 --- a/src/cobalt/script/mozjs/mozjs_callback_function.h +++ b/src/cobalt/script/mozjs/mozjs_callback_function.h
@@ -27,7 +27,9 @@ #include "cobalt/script/callback_function.h" #include "cobalt/script/mozjs/conversion_helpers.h" #include "cobalt/script/mozjs/convert_callback_return_value.h" +#include "cobalt/script/mozjs/util/exception_helpers.h" #include "cobalt/script/mozjs/weak_heap_object.h" +#include "nb/memory_scope.h" #include "third_party/mozjs/js/src/jsapi.h" #include "third_party/mozjs/js/src/jscntxt.h" @@ -57,13 +59,15 @@ CallbackResult<R> Run() const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -78,7 +82,8 @@ JSBool call_result = JS::Call(context_, this_value, function, 0, NULL, return_value.address()); if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value); @@ -110,13 +115,15 @@ CallbackResult<R> Run( typename base::internal::CallbackParamTraits<A1>::ForwardType a1) const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -136,7 +143,8 @@ JSBool call_result = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value); @@ -169,13 +177,15 @@ typename base::internal::CallbackParamTraits<A1>::ForwardType a1, typename base::internal::CallbackParamTraits<A2>::ForwardType a2) const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -196,7 +206,8 @@ JSBool call_result = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value); @@ -230,13 +241,15 @@ typename base::internal::CallbackParamTraits<A2>::ForwardType a2, typename base::internal::CallbackParamTraits<A3>::ForwardType a3) const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -258,7 +271,8 @@ JSBool call_result = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value); @@ -293,13 +307,15 @@ typename base::internal::CallbackParamTraits<A3>::ForwardType a3, typename base::internal::CallbackParamTraits<A4>::ForwardType a4) const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -322,7 +338,8 @@ JSBool call_result = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value); @@ -359,13 +376,15 @@ typename base::internal::CallbackParamTraits<A4>::ForwardType a4, typename base::internal::CallbackParamTraits<A5>::ForwardType a5) const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -389,7 +408,8 @@ JSBool call_result = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value); @@ -427,13 +447,15 @@ typename base::internal::CallbackParamTraits<A5>::ForwardType a5, typename base::internal::CallbackParamTraits<A6>::ForwardType a6) const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -458,7 +480,8 @@ JSBool call_result = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value); @@ -497,13 +520,15 @@ typename base::internal::CallbackParamTraits<A6>::ForwardType a6, typename base::internal::CallbackParamTraits<A7>::ForwardType a7) const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -529,7 +554,8 @@ JSBool call_result = JS::Call(context_, this_value, function, kNumArguments, args, return_value.address()); if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
diff --git a/src/cobalt/script/mozjs/mozjs_callback_function.h.pump b/src/cobalt/script/mozjs/mozjs_callback_function.h.pump index 810dcc5..9bfb5f2 100644 --- a/src/cobalt/script/mozjs/mozjs_callback_function.h.pump +++ b/src/cobalt/script/mozjs/mozjs_callback_function.h.pump
@@ -32,7 +32,9 @@ #include "cobalt/script/callback_function.h" #include "cobalt/script/mozjs/conversion_helpers.h" #include "cobalt/script/mozjs/convert_callback_return_value.h" +#include "cobalt/script/mozjs/util/exception_helpers.h" #include "cobalt/script/mozjs/weak_heap_object.h" +#include "nb/memory_scope.h" #include "third_party/mozjs/js/src/jsapi.h" #include "third_party/mozjs/js/src/jscntxt.h" @@ -76,13 +78,15 @@ typename base::internal::CallbackParamTraits<A$(ARG)>::ForwardType a$(ARG)]]) const OVERRIDE { + TRACK_MEMORY_SCOPE("Javascript"); + TRACE_EVENT0("cobalt::script::mozjs", "MozjsCallbackFunction::Run"); CallbackResult<R> callback_result; + JSAutoRequest auto_request(context_); JS::RootedObject function(context_, weak_function_.Get()); if (!function) { DLOG(WARNING) << "Function was garbage collected."; callback_result.exception = true; } else { - JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, function); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); @@ -109,7 +113,8 @@ ]] if (!call_result) { - DLOG(WARNING) << "Exception in callback."; + DLOG(WARNING) << "Exception in callback: " + << util::GetExceptionString(context_); callback_result.exception = true; } else { callback_result = ConvertCallbackReturnValue<R>(context_, return_value);
diff --git a/src/cobalt/script/mozjs/mozjs_engine.cc b/src/cobalt/script/mozjs/mozjs_engine.cc index be18f1e..7b62f3f 100644 --- a/src/cobalt/script/mozjs/mozjs_engine.cc +++ b/src/cobalt/script/mozjs/mozjs_engine.cc
@@ -18,8 +18,10 @@ #include <algorithm> +#include "base/debug/trace_event.h" #include "base/logging.h" #include "cobalt/base/c_val.h" +#include "cobalt/browser/stack_size_constants.h" #include "cobalt/script/mozjs/mozjs_global_environment.h" #include "third_party/mozjs/cobalt_config/include/jscustomallocator.h" #include "third_party/mozjs/js/src/jsapi.h" @@ -86,15 +88,33 @@ "Total JavaScript engine registered.") { } +// Pretend we always preserve wrappers since we never call +// SetPreserveWrapperCallback anywhere else. This is necessary for +// TryPreserveReflector called by WeakMap to not crash. Disabling +// bindings to WeakMap does not appear to be an easy option because +// of its use in selfhosted.js. See bugzilla discussion linked where +// they decided to include a similar dummy in the mozjs shell. +// https://bugzilla.mozilla.org/show_bug.cgi?id=829798 +bool DummyPreserveWrapperCallback(JSContext *cx, JSObject *obj) { + return true; +} + } // namespace MozjsEngine::MozjsEngine() : accumulated_extra_memory_cost_(0) { + TRACE_EVENT0("cobalt::script", "MozjsEngine::MozjsEngine()"); // TODO: Investigate the benefit of helper threads and things like // parallel compilation. runtime_ = JS_NewRuntime(kGarbageCollectionThresholdBytes, JS_NO_HELPER_THREADS); CHECK(runtime_); + // Sets the size of the native stack that should not be exceeded. + // Setting three quarters of the web module stack size to ensure that native + // stack won't exceed the stack size. + JS_SetNativeStackQuota(runtime_, + cobalt::browser::kWebModuleStackSize / 4 * 3); + JS_SetRuntimePrivate(runtime_, this); JS_SetSecurityCallbacks(runtime_, &security_callbacks); @@ -117,6 +137,8 @@ // Callback to be called during garbage collection during the sweep phase. JS_SetFinalizeCallback(runtime_, &MozjsEngine::FinalizeCallback); + js::SetPreserveWrapperCallback(runtime_, DummyPreserveWrapperCallback); + EngineStats::GetInstance()->EngineCreated(); } @@ -127,11 +149,13 @@ } scoped_refptr<GlobalEnvironment> MozjsEngine::CreateGlobalEnvironment() { + TRACE_EVENT0("cobalt::script", "MozjsEngine::CreateGlobalEnvironment()"); DCHECK(thread_checker_.CalledOnValidThread()); return new MozjsGlobalEnvironment(runtime_); } void MozjsEngine::CollectGarbage() { + TRACE_EVENT0("cobalt::script", "MozjsEngine::CollectGarbage()"); DCHECK(thread_checker_.CalledOnValidThread()); JS_GC(runtime_); } @@ -176,15 +200,18 @@ MozjsGlobalEnvironment* global_environment = MozjsGlobalEnvironment::GetFromContext(engine->contexts_[i]); if (status == JSGC_BEGIN) { + TRACE_EVENT_BEGIN0("cobalt::script", "SpiderMonkey Garbage Collection"); global_environment->BeginGarbageCollection(); } else if (status == JSGC_END) { global_environment->EndGarbageCollection(); + TRACE_EVENT_END0("cobalt::script", "SpiderMonkey Garbage Collection"); } } } void MozjsEngine::FinalizeCallback(JSFreeOp* free_op, JSFinalizeStatus status, JSBool is_compartment) { + TRACE_EVENT0("cobalt::script", "MozjsEngine::FinalizeCallback()"); MozjsEngine* engine = static_cast<MozjsEngine*>(JS_GetRuntimePrivate(free_op->runtime())); DCHECK(engine->thread_checker_.CalledOnValidThread()); @@ -200,6 +227,7 @@ } // namespace mozjs scoped_ptr<JavaScriptEngine> JavaScriptEngine::CreateEngine() { + TRACE_EVENT0("cobalt::script", "JavaScriptEngine::CreateEngine()"); return make_scoped_ptr<JavaScriptEngine>(new mozjs::MozjsEngine()); }
diff --git a/src/cobalt/script/mozjs/mozjs_exception_state.cc b/src/cobalt/script/mozjs/mozjs_exception_state.cc index 9b2a5ec..39a611a 100644 --- a/src/cobalt/script/mozjs/mozjs_exception_state.cc +++ b/src/cobalt/script/mozjs/mozjs_exception_state.cc
@@ -44,6 +44,8 @@ case kURIError: return JSEXN_URIERR; } + NOTREACHED(); + return JSEXN_ERR; } // JSErrorCallback.
diff --git a/src/cobalt/script/mozjs/mozjs_global_environment.cc b/src/cobalt/script/mozjs/mozjs_global_environment.cc index d7456fe..b9c05a9 100644 --- a/src/cobalt/script/mozjs/mozjs_global_environment.cc +++ b/src/cobalt/script/mozjs/mozjs_global_environment.cc
@@ -28,6 +28,7 @@ #include "cobalt/script/mozjs/proxy_handler.h" #include "cobalt/script/mozjs/referenced_object_map.h" #include "cobalt/script/mozjs/util/exception_helpers.h" +#include "nb/memory_scope.h" #include "third_party/mozjs/js/src/jsfriendapi.h" #include "third_party/mozjs/js/src/jsfun.h" #include "third_party/mozjs/js/src/jsobj.h" @@ -128,6 +129,7 @@ environment_settings_(NULL), last_error_message_(NULL), eval_enabled_(false) { + TRACK_MEMORY_SCOPE("Javascript"); context_ = JS_NewContext(runtime, kStackChunkSize); DCHECK(context_); // Set a pointer to this class inside the JSContext. @@ -166,6 +168,7 @@ } void MozjsGlobalEnvironment::CreateGlobalObject() { + TRACK_MEMORY_SCOPE("Javascript"); DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!global_object_proxy_); @@ -194,45 +197,31 @@ bool MozjsGlobalEnvironment::EvaluateScript( const scoped_refptr<SourceCode>& source_code, std::string* out_result_utf8) { + TRACK_MEMORY_SCOPE("Javascript"); DCHECK(thread_checker_.CalledOnValidThread()); - MozjsSourceCode* mozjs_source_code = - base::polymorphic_downcast<MozjsSourceCode*>(source_code.get()); - - const std::string& script = mozjs_source_code->source_utf8(); - const base::SourceLocation location = mozjs_source_code->location(); JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, global_object_proxy_); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); JS::RootedValue result_value(context_); + std::string error_message; last_error_message_ = &error_message; - JS::RootedObject global_object( - context_, js::GetProxyTargetObject(global_object_proxy_)); - size_t length = script.size(); - jschar* inflated_buffer = - js::InflateUTF8String(context_, script.c_str(), &length); - DCHECK(inflated_buffer); - bool success = false; - if (inflated_buffer) { - success = JS_EvaluateUCScript(context_, global_object, inflated_buffer, - length, location.file_path.c_str(), - location.line_number, result_value.address()); - js_free(inflated_buffer); - } - + bool success = EvaluateScriptInternal(source_code, &result_value); if (out_result_utf8) { if (success) { MozjsExceptionState exception_state(context_); FromJSValue(context_, result_value, kNoConversionFlags, &exception_state, out_result_utf8); - } else { + } else if (last_error_message_) { *out_result_utf8 = *last_error_message_; + } else { + DLOG(ERROR) << "Script execution failed."; } } - JS_RestoreExceptionState(context_, previous_exception_state); last_error_message_ = NULL; + JS_RestoreExceptionState(context_, previous_exception_state); return success; } @@ -240,15 +229,14 @@ const scoped_refptr<SourceCode>& source_code, const scoped_refptr<Wrappable>& owning_object, base::optional<OpaqueHandleHolder::Reference>* out_opaque_handle) { + TRACK_MEMORY_SCOPE("Javascript"); DCHECK(thread_checker_.CalledOnValidThread()); JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, global_object_proxy_); JSExceptionState* previous_exception_state = JS_SaveExceptionState(context_); JS::RootedValue result_value(context_); - if (!EvaluateScriptInternal(source_code, &result_value)) { - return false; - } - if (out_opaque_handle) { + bool success = EvaluateScriptInternal(source_code, &result_value); + if (success && out_opaque_handle) { JS::RootedObject js_object(context_); JS_ValueToObject(context_, result_value, js_object.address()); MozjsObjectHandleHolder mozjs_object_holder(js_object, context_, @@ -256,12 +244,13 @@ out_opaque_handle->emplace(owning_object.get(), mozjs_object_holder); } JS_RestoreExceptionState(context_, previous_exception_state); - return true; + return success; } bool MozjsGlobalEnvironment::EvaluateScriptInternal( const scoped_refptr<SourceCode>& source_code, JS::MutableHandleValue out_result) { + TRACK_MEMORY_SCOPE("Javascript"); DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(global_object_proxy_); MozjsSourceCode* mozjs_source_code = @@ -276,13 +265,14 @@ size_t length = script.size(); jschar* inflated_buffer = js::InflateUTF8String(context_, script.c_str(), &length); - DCHECK(inflated_buffer); bool success = false; if (inflated_buffer) { success = JS_EvaluateUCScript(context_, global_object, inflated_buffer, length, location.file_path.c_str(), location.line_number, out_result.address()); js_free(inflated_buffer); + } else { + DLOG(ERROR) << "Malformed UTF-8 script."; } return success; @@ -307,6 +297,7 @@ void MozjsGlobalEnvironment::AllowGarbageCollection( const scoped_refptr<Wrappable>& wrappable) { + TRACK_MEMORY_SCOPE("Javascript"); DCHECK(thread_checker_.CalledOnValidThread()); CachedWrapperMultiMap::iterator it = kept_alive_objects_.find(wrappable.get()); @@ -336,6 +327,7 @@ void MozjsGlobalEnvironment::Bind(const std::string& identifier, const scoped_refptr<Wrappable>& impl) { + TRACK_MEMORY_SCOPE("Javascript"); JSAutoRequest auto_request(context_); JSAutoCompartment auto_compartment(context_, global_object_proxy_); @@ -358,12 +350,14 @@ } void MozjsGlobalEnvironment::DoSweep() { + TRACK_MEMORY_SCOPE("Javascript"); weak_object_manager_.SweepUnmarkedObjects(); // Remove NULL references after sweeping weak references. referenced_objects_->RemoveNullReferences(); } void MozjsGlobalEnvironment::BeginGarbageCollection() { + TRACK_MEMORY_SCOPE("Javascript"); // It's possible that a GC could be triggered from within the // BeginGarbageCollection callback. Only create the OpaqueRootState the first // time we enter. @@ -477,6 +471,7 @@ } JSBool MozjsGlobalEnvironment::CheckEval(JSContext* context) { + TRACK_MEMORY_SCOPE("Javascript"); MozjsGlobalEnvironment* global_object_proxy = GetFromContext(context); DCHECK(global_object_proxy); if (!global_object_proxy->report_eval_.is_null()) {
diff --git a/src/cobalt/script/mozjs/mozjs_trace_logging.cc b/src/cobalt/script/mozjs/mozjs_trace_logging.cc new file mode 100644 index 0000000..2e11ce8 --- /dev/null +++ b/src/cobalt/script/mozjs/mozjs_trace_logging.cc
@@ -0,0 +1,157 @@ +/* + * Copyright 2016 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 "third_party/mozjs/js/src/TraceLogging.h" + +#include "base/debug/trace_event.h" +#include "base/memory/singleton.h" + +namespace js { + +namespace { + +// Container class to transform the 3rd-party-declared TraceLogging class +// into our Singleton type. +class TraceLoggingContainer { + public: + TraceLoggingContainer() {} + + static TraceLoggingContainer* GetInstance() { + return Singleton<TraceLoggingContainer, + StaticMemorySingletonTraits<TraceLoggingContainer> >::get(); + } + + TraceLogging* GetLogger() { + return &logger_; + } + + private: + TraceLogging logger_; + + DISALLOW_COPY_AND_ASSIGN(TraceLoggingContainer); +}; + +const char kTraceLoggingCategory[] = "JavaScript"; + +} // namespace + +const char* const TraceLogging::type_name[] = { + "IonCompile", + "IonCompile", + "IonCannon", + "IonCannon", + "IonCannon", + "IonSideCannon", + "IonSideCannon", + "IonSideCannon", + "YarrJIT", + "YarrJIT", + "JMSafepoint", + "JMSafepoint", + "JMNormal", + "JMNormal", + "JMCompile", + "JMCompile", + "GarbageCollect", + "GarbageCollect", + "Interpreter", + "Interpreter", + "Info", +}; + +// None of the member variables will be needed. Instead, logging will be +// funneled to the trace event system. +TraceLogging::TraceLogging() { +} + +TraceLogging::~TraceLogging() { +} + +// static +TraceLogging* TraceLogging::defaultLogger() { + return TraceLoggingContainer::GetInstance()->GetLogger(); +} + +// static +void TraceLogging::releaseDefaultLogger() { + // Do nothing. Lifetime is managed by the TraceLoggingContainer singleton. +} + +void TraceLogging::log(Type type, const char* filename, unsigned int line) { + // Unfortunately, we don't have access to the enum, so can't declare a + // "count"-type enum. Instead, INFO is assumed to be the last enumeration. + COMPILE_ASSERT(ARRAYSIZE_UNSAFE(type_name)-1 == Type::INFO, array_mismatch); + + switch (type) { + // "Start" types + case ION_COMPILE_START: + case ION_CANNON_START: + case ION_SIDE_CANNON_START: + case YARR_JIT_START: + case JM_SAFEPOINT_START: + case JM_START: + case JM_COMPILE_START: + case GC_START: + case INTERPRETER_START: + if (filename != NULL) { + TRACE_EVENT_BEGIN2(kTraceLoggingCategory, type_name[type], + "file", TRACE_STR_COPY(filename), + "line", line); + } else { + TRACE_EVENT_BEGIN0(kTraceLoggingCategory, type_name[type]); + } + break; + + // Ignored types + case INFO: + break; + + // "Stop" types + default: + TRACE_EVENT_END0(kTraceLoggingCategory, type_name[type]); + break; + } +} + +void TraceLogging::log(Type type, JSScript* script) { + log(type, script->filename(), script->lineno); +} + +void TraceLogging::log(const char* log) { + UNREFERENCED_PARAMETER(log); +} + +void TraceLogging::log(Type type) { + log(type, NULL, 0); +} + +void TraceLogging::flush() { +} + +// Helper functions declared in TraceLogging.h +void TraceLog(TraceLogging* logger, TraceLogging::Type type, JSScript* script) { + logger->log(type, script); +} + +void TraceLog(TraceLogging* logger, const char* log) { + logger->log(log); +} + +void TraceLog(TraceLogging* logger, TraceLogging::Type type) { + logger->log(type); +} + +} // namespace js
diff --git a/src/cobalt/script/mozjs/mozjs_user_object_holder.h b/src/cobalt/script/mozjs/mozjs_user_object_holder.h index 2ee384f..c70ad1a 100644 --- a/src/cobalt/script/mozjs/mozjs_user_object_holder.h +++ b/src/cobalt/script/mozjs/mozjs_user_object_holder.h
@@ -51,6 +51,7 @@ wrapper_factory_(wrapper_factory) {} void RegisterOwner(Wrappable* owner) OVERRIDE { + JSAutoRequest auto_request(context_); JS::RootedObject owned_object(context_, js_object()); DLOG_IF(WARNING, !owned_object) << "Owned object has been garbage collected."; @@ -65,6 +66,7 @@ void DeregisterOwner(Wrappable* owner) OVERRIDE { // |owner| may be in the process of being destructed, so don't use it. + JSAutoRequest auto_request(context_); JS::RootedObject owned_object(context_, js_object()); if (owned_object) { MozjsGlobalEnvironment* global_environment = @@ -82,6 +84,7 @@ scoped_ptr<BaseClass> MakeCopy() const OVERRIDE { DCHECK(object_handle_); + JSAutoRequest auto_request(context_); JS::RootedObject rooted_object(context_, js_object()); return make_scoped_ptr<BaseClass>( new MozjsUserObjectHolder(rooted_object, context_, wrapper_factory_));
diff --git a/src/cobalt/script/mozjs/opaque_root_tracker.cc b/src/cobalt/script/mozjs/opaque_root_tracker.cc index 1ae8d32..dfc405e 100644 --- a/src/cobalt/script/mozjs/opaque_root_tracker.cc +++ b/src/cobalt/script/mozjs/opaque_root_tracker.cc
@@ -20,6 +20,7 @@ #include <vector> #include "cobalt/script/mozjs/weak_heap_object.h" +#include "third_party/mozjs/js/src/jsapi.h" namespace cobalt { namespace script { @@ -45,6 +46,7 @@ } ~OpaqueRootStateImpl() { + JSAutoRequest auto_request(context_); for (ReferencedObjectPairVector::iterator it = referenced_objects_.begin(); it != referenced_objects_.end(); ++it) { if (it->second.Get()) {
diff --git a/src/cobalt/script/mozjs/util/exception_helpers.cc b/src/cobalt/script/mozjs/util/exception_helpers.cc index 030f45a..3a4b80c 100644 --- a/src/cobalt/script/mozjs/util/exception_helpers.cc +++ b/src/cobalt/script/mozjs/util/exception_helpers.cc
@@ -20,6 +20,7 @@ #include "cobalt/script/mozjs/conversion_helpers.h" #include "cobalt/script/mozjs/mozjs_exception_state.h" +#include "third_party/mozjs/js/src/jsapi.h" #include "third_party/mozjs/js/src/jsdbgapi.h" #include "third_party/mozjs/js/src/jsscript.h" @@ -27,7 +28,27 @@ namespace script { namespace mozjs { namespace util { +std::string GetExceptionString(JSContext* context) { + if (!JS_IsExceptionPending(context)) { + return std::string("No exception pending."); + } + JS::RootedValue exception(context); + JS_GetPendingException(context, exception.address()); + JS_ReportPendingException(context); + return GetExceptionString(context, exception); +} + +std::string GetExceptionString(JSContext* context, + JS::HandleValue exception) { + std::string exception_string; + MozjsExceptionState exception_state(context); + FromJSValue(context, exception, kNoConversionFlags, &exception_state, + &exception_string); + return exception_string; +} + std::vector<StackFrame> GetStackTrace(JSContext* context, int max_frames) { + JSAutoRequest auto_request(context); JS::StackDescription* stack_description = JS::DescribeStack(context, max_frames); if (max_frames == 0) {
diff --git a/src/cobalt/script/mozjs/util/exception_helpers.h b/src/cobalt/script/mozjs/util/exception_helpers.h index 75dff27..7e5e563 100644 --- a/src/cobalt/script/mozjs/util/exception_helpers.h +++ b/src/cobalt/script/mozjs/util/exception_helpers.h
@@ -16,6 +16,7 @@ #ifndef COBALT_SCRIPT_MOZJS_UTIL_EXCEPTION_HELPERS_H_ #define COBALT_SCRIPT_MOZJS_UTIL_EXCEPTION_HELPERS_H_ +#include <string> #include <vector> #include "cobalt/script/stack_frame.h" @@ -25,6 +26,10 @@ namespace script { namespace mozjs { namespace util { +std::string GetExceptionString(JSContext* context); + +std::string GetExceptionString(JSContext* context, JS::HandleValue exception); + std::vector<StackFrame> GetStackTrace(JSContext* context, int max_frames); } // namespace util } // namespace mozjs
diff --git a/src/cobalt/script/mozjs/wrapper_factory.cc b/src/cobalt/script/mozjs/wrapper_factory.cc index 72b2edb..d533458 100644 --- a/src/cobalt/script/mozjs/wrapper_factory.cc +++ b/src/cobalt/script/mozjs/wrapper_factory.cc
@@ -79,8 +79,14 @@ new MozjsWrapperHandle(wrapper_private)); } -bool WrapperFactory::DoesObjectImplementInterface(JSObject* object, +bool WrapperFactory::DoesObjectImplementInterface(JS::HandleObject object, base::TypeId type_id) const { + // If the object doesn't have a wrapper private which means it is not a + // platform object, so the object doesn't implement the interface. + if (!WrapperPrivate::HasWrapperPrivate(context_, object)) { + return false; + } + WrappableTypeFunctionsHashMap::const_iterator it = wrappable_type_functions_.find(type_id); if (it == wrappable_type_functions_.end()) {
diff --git a/src/cobalt/script/mozjs/wrapper_factory.h b/src/cobalt/script/mozjs/wrapper_factory.h index 828bdac..79be1b7 100644 --- a/src/cobalt/script/mozjs/wrapper_factory.h +++ b/src/cobalt/script/mozjs/wrapper_factory.h
@@ -51,7 +51,8 @@ // Returns true if this JSObject is a Wrapper object. bool IsWrapper(JS::HandleObject wrapper) const; - bool DoesObjectImplementInterface(JSObject*, base::TypeId) const; + bool DoesObjectImplementInterface(JS::HandleObject object, + base::TypeId id) const; private: struct WrappableTypeFunctions {
diff --git a/src/cobalt/script/mozjs/wrapper_private.cc b/src/cobalt/script/mozjs/wrapper_private.cc index 7dc1410..1bf15ca 100644 --- a/src/cobalt/script/mozjs/wrapper_private.cc +++ b/src/cobalt/script/mozjs/wrapper_private.cc
@@ -20,6 +20,7 @@ #include "cobalt/script/mozjs/proxy_handler.h" #include "cobalt/script/mozjs/referenced_object_map.h" #include "third_party/mozjs/js/src/jsapi.h" +#include "third_party/mozjs/js/src/jsobj.h" #include "third_party/mozjs/js/src/jsproxy.h" namespace cobalt { @@ -65,6 +66,17 @@ } // static +bool WrapperPrivate::HasWrapperPrivate(JSContext* context, + JS::HandleObject object) { + if (js::IsProxy(object)) { + JS::RootedObject target_object(context, js::GetProxyTargetObject(object)); + return WrapperPrivate::HasWrapperPrivate(context, target_object); + } + + return object->hasPrivate(); +} + +// static WrapperPrivate* WrapperPrivate::GetFromWrappable( const scoped_refptr<Wrappable>& wrappable, JSContext* context, WrapperFactory* wrapper_factory) {
diff --git a/src/cobalt/script/mozjs/wrapper_private.h b/src/cobalt/script/mozjs/wrapper_private.h index d7ebfb1..ae0286d 100644 --- a/src/cobalt/script/mozjs/wrapper_private.h +++ b/src/cobalt/script/mozjs/wrapper_private.h
@@ -69,6 +69,9 @@ GetReachableWrappablesFunction()); } + // Return true if the object has wrapper private. + static bool HasWrapperPrivate(JSContext* context, JS::HandleObject object); + // Get the WrapperPrivate associated with the given Wrappable. A new JSObject // and WrapperPrivate object may be created. static WrapperPrivate* GetFromWrappable(
diff --git a/src/cobalt/script/script.gyp b/src/cobalt/script/script.gyp index c827de2..46cc245 100644 --- a/src/cobalt/script/script.gyp +++ b/src/cobalt/script/script.gyp
@@ -38,6 +38,7 @@ ], 'dependencies': [ '<(DEPTH)/cobalt/base/base.gyp:base', + '<(DEPTH)/nb/nb.gyp:nb', ] }, {
diff --git a/src/cobalt/script/script_runner.cc b/src/cobalt/script/script_runner.cc index d680966..3900885 100644 --- a/src/cobalt/script/script_runner.cc +++ b/src/cobalt/script/script_runner.cc
@@ -19,12 +19,51 @@ #include "base/logging.h" #include "cobalt/script/global_environment.h" #include "cobalt/script/source_code.h" +#if defined(OS_STARBOARD) +#include "starboard/configuration.h" +#if SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +#define HANDLE_CORE_DUMP +#include "base/lazy_instance.h" +#include "starboard/ps4/core_dump_handler.h" +#endif // SB_HAS(CORE_DUMP_HANDLER_SUPPORT) +#endif // defined(OS_STARBOARD) namespace cobalt { namespace script { namespace { +#if defined(HANDLE_CORE_DUMP) + +class ScriptRunnerLog { + public: + ScriptRunnerLog() : success_count_(0), fail_count_(0) { + SbCoreDumpRegisterHandler(CoreDumpHandler, this); + } + ~ScriptRunnerLog() { SbCoreDumpUnregisterHandler(CoreDumpHandler, this); } + + static void CoreDumpHandler(void* context) { + SbCoreDumpLogInteger( + "ScriptRunner successful executions", + static_cast<ScriptRunnerLog*>(context)->success_count_); + SbCoreDumpLogInteger("ScriptRunner failed executions", + static_cast<ScriptRunnerLog*>(context)->fail_count_); + } + + void IncrementSuccessCount() { success_count_++; } + void IncrementFailCount() { fail_count_++; } + + private: + int success_count_; + int fail_count_; + DISALLOW_COPY_AND_ASSIGN(ScriptRunnerLog); +}; + +base::LazyInstance<ScriptRunnerLog> script_runner_log = + LAZY_INSTANCE_INITIALIZER; + +#endif // defined(HANDLE_CORE_DUMP) + class ScriptRunnerImpl : public ScriptRunner { public: explicit ScriptRunnerImpl( @@ -53,8 +92,14 @@ std::string result; if (!global_environment_->EvaluateScript(source_code, &result)) { DLOG(WARNING) << "Failed to execute JavaScript: " << result; +#if defined(HANDLE_CORE_DUMP) + script_runner_log.Get().IncrementFailCount(); +#endif return ""; } +#if defined(HANDLE_CORE_DUMP) + script_runner_log.Get().IncrementSuccessCount(); +#endif return result; }
diff --git a/src/cobalt/speech/SpeechRecognition.idl b/src/cobalt/speech/SpeechRecognition.idl index 1db9e80..1bb1a24 100644 --- a/src/cobalt/speech/SpeechRecognition.idl +++ b/src/cobalt/speech/SpeechRecognition.idl
@@ -28,7 +28,7 @@ attribute unsigned long maxAlternatives; // methods to drive the speech interaction - void start(); + [RaisesException] void start(); void stop(); void abort();
diff --git a/src/cobalt/speech/SpeechRecognitionResult.idl b/src/cobalt/speech/SpeechRecognitionResult.idl index 6cc3071..ef1e97d 100644 --- a/src/cobalt/speech/SpeechRecognitionResult.idl +++ b/src/cobalt/speech/SpeechRecognitionResult.idl
@@ -19,5 +19,6 @@ interface SpeechRecognitionResult { readonly attribute unsigned long length; getter SpeechRecognitionAlternative item(unsigned long index); - readonly attribute boolean final; + // isFinal is different from the spec., but follows with Chromium. + readonly attribute boolean isFinal; };
diff --git a/src/cobalt/speech/audio_encoder_flac.cc b/src/cobalt/speech/audio_encoder_flac.cc new file mode 100644 index 0000000..16c5640 --- /dev/null +++ b/src/cobalt/speech/audio_encoder_flac.cc
@@ -0,0 +1,130 @@ +/* + * Copyright 2016 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/speech/audio_encoder_flac.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_number_conversions.h" + +namespace cobalt { +namespace speech { + +namespace { +const char kContentTypeFLAC[] = "audio/x-flac; rate="; +const int kFLACCompressionLevel = 0; // 0 for speed +const int kBitsPerSample = 16; +const float kMaxInt16AsFloat32 = 32767.0f; +} // namespace + +AudioEncoderFlac::AudioEncoderFlac(int sample_rate) + : encoder_(FLAC__stream_encoder_new()) { + DCHECK(encoder_); + + // Set the number of channels to be encoded. + FLAC__stream_encoder_set_channels(encoder_, 1); + // Set the sample resolution of the input to be encoded. + FLAC__stream_encoder_set_bits_per_sample(encoder_, kBitsPerSample); + // Set the sample rate (in Hz) of the input to be encoded. + FLAC__stream_encoder_set_sample_rate(encoder_, + static_cast<uint32>(sample_rate)); + // Set the compression level. A higher level usually means more computation + // but higher compression. + FLAC__stream_encoder_set_compression_level(encoder_, kFLACCompressionLevel); + + // Initialize the encoder instance to encode native FLAC stream. + FLAC__StreamEncoderInitStatus encoder_status = + FLAC__stream_encoder_init_stream(encoder_, WriteCallback, NULL, NULL, + NULL, this); + DCHECK_EQ(encoder_status, FLAC__STREAM_ENCODER_INIT_STATUS_OK); +} + +AudioEncoderFlac::~AudioEncoderFlac() { + DCHECK(thread_checker_.CalledOnValidThread()); + FLAC__stream_encoder_delete(encoder_); +} + +void AudioEncoderFlac::Encode(const ShellAudioBus* audio_bus) { + DCHECK(thread_checker_.CalledOnValidThread()); + + DCHECK_EQ(audio_bus->channels(), 1); + uint32 frames = static_cast<uint32>(audio_bus->frames()); + scoped_array<FLAC__int32> flac_samples(new FLAC__int32[frames]); + for (uint32 i = 0; i < frames; ++i) { + if (audio_bus->sample_type() == ShellAudioBus::kFloat32) { + flac_samples[i] = static_cast<FLAC__int32>( + audio_bus->GetFloat32Sample(0, i) * kMaxInt16AsFloat32); + } else { + DCHECK_EQ(audio_bus->sample_type(), ShellAudioBus::kInt16); + flac_samples[i] = + static_cast<FLAC__int32>(audio_bus->GetInt16Sample(0, i)); + } + } + + FLAC__int32* flac_samples_ptr = flac_samples.get(); + // Submit data for encoding. + FLAC__bool success = + FLAC__stream_encoder_process(encoder_, &flac_samples_ptr, frames); + DCHECK(success); +} + +void AudioEncoderFlac::Finish() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Finish the encoding. It causes the encoder to encode any data still in + // its input pipe, and finally reset the encoder to the unintialized state. + FLAC__stream_encoder_finish(encoder_); +} + +std::string AudioEncoderFlac::GetMimeType() const { + DCHECK(thread_checker_.CalledOnValidThread()); + + return std::string(kContentTypeFLAC) + + base::UintToString(FLAC__stream_encoder_get_sample_rate(encoder_)); +} + +std::string AudioEncoderFlac::GetAndClearAvailableEncodedData() { + DCHECK(thread_checker_.CalledOnValidThread()); + + std::string result = encoded_data_; + encoded_data_.clear(); + return result; +} + +// A write callback which will be called anytime there is a raw encoded data to +// write. The call to FLAC__stream_encoder_init_stream() currently will also +// immediately call the write callback several times, once with the FLAC +// signature, and once for each encoded metadata block. +FLAC__StreamEncoderWriteStatus AudioEncoderFlac::WriteCallback( + const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], size_t bytes, + unsigned int samples, unsigned int current_frame, void* client_data) { + UNREFERENCED_PARAMETER(encoder); + UNREFERENCED_PARAMETER(samples); + UNREFERENCED_PARAMETER(current_frame); + + AudioEncoderFlac* audio_encoder = + reinterpret_cast<AudioEncoderFlac*>(client_data); + DCHECK(audio_encoder); + DCHECK(audio_encoder->thread_checker_.CalledOnValidThread()); + + audio_encoder->encoded_data_.append(reinterpret_cast<const char*>(buffer), + bytes); + + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + +} // namespace speech +} // namespace cobalt
diff --git a/src/cobalt/speech/audio_encoder_flac.h b/src/cobalt/speech/audio_encoder_flac.h new file mode 100644 index 0000000..62fcfb1 --- /dev/null +++ b/src/cobalt/speech/audio_encoder_flac.h
@@ -0,0 +1,66 @@ +/* + * Copyright 2016 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_SPEECH_AUDIO_ENCODER_FLAC_H_ +#define COBALT_SPEECH_AUDIO_ENCODER_FLAC_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/threading/thread_checker.h" +#include "media/base/shell_audio_bus.h" +#include "third_party/flac/include/FLAC/stream_encoder.h" + +namespace cobalt { +namespace speech { + +// Encode raw audio to using FLAC codec. +class AudioEncoderFlac { + public: + typedef ::media::ShellAudioBus ShellAudioBus; + + explicit AudioEncoderFlac(int sample_rate); + ~AudioEncoderFlac(); + + // Encode raw audio data. + void Encode(const ShellAudioBus* audio_bus); + // Finish encoding. + void Finish(); + + // Returns mime Type of audio data. + std::string GetMimeType() const; + // Returns and clears the available encoded audio data. + std::string GetAndClearAvailableEncodedData(); + + private: + // FLAC encoder callback. + static FLAC__StreamEncoderWriteStatus WriteCallback( + const FLAC__StreamEncoder* encoder, const FLAC__byte buffer[], + size_t bytes, unsigned int samples, unsigned int current_frame, + void* client_data); + + base::ThreadChecker thread_checker_; + // FLAC encoder. + FLAC__StreamEncoder* encoder_; + // Cached encoded data. + std::string encoded_data_; +}; + +} // namespace speech +} // namespace cobalt + +#endif // COBALT_SPEECH_AUDIO_ENCODER_FLAC_H_
diff --git a/src/cobalt/speech/endpointer_delegate.cc b/src/cobalt/speech/endpointer_delegate.cc new file mode 100644 index 0000000..f8a826f --- /dev/null +++ b/src/cobalt/speech/endpointer_delegate.cc
@@ -0,0 +1,75 @@ +/* + * Copyright 2016 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/speech/endpointer_delegate.h" + +#include "base/time.h" + +namespace cobalt { +namespace speech { + +namespace { +const int kEndPointerEstimationTimeInMillisecond = 300; +// If used in noisy conditions, the endpointer should be started and run in the +// EnvironmentEstimation mode, for at least 200ms, before switching to +// UserInputMode. +COMPILE_ASSERT(kEndPointerEstimationTimeInMillisecond >= 200, + in_environment_estimation_mode_for_at_least_200ms); +} // namespace + +EndPointerDelegate::EndPointerDelegate(int sample_rate) + : endpointer_(sample_rate), + num_samples_recorded_(0), + is_first_time_sound_started_(false) {} + +EndPointerDelegate::~EndPointerDelegate() {} + +void EndPointerDelegate::Start() { + num_samples_recorded_ = 0; + is_first_time_sound_started_ = false; + + endpointer_.StartSession(); + endpointer_.SetEnvironmentEstimationMode(); +} + +void EndPointerDelegate::Stop() { endpointer_.EndSession(); } + +bool EndPointerDelegate::IsFirstTimeSoundStarted( + const ShellAudioBus& audio_bus) { + if (is_first_time_sound_started_) { + return false; + } + + num_samples_recorded_ += static_cast<int>(audio_bus.frames()); + if (endpointer_.IsEstimatingEnvironment() && + num_samples_recorded_ * 1000 / endpointer_.sample_rate() > + kEndPointerEstimationTimeInMillisecond) { + // Switch to user input mode. + endpointer_.SetUserInputMode(); + } + + endpointer_.ProcessAudio(audio_bus, NULL); + int64_t ep_time; + content::EpStatus status = endpointer_.Status(&ep_time); + if (status == content::EP_POSSIBLE_ONSET) { + is_first_time_sound_started_ = true; + } + + return is_first_time_sound_started_; +} + +} // namespace speech +} // namespace cobalt
diff --git a/src/cobalt/speech/endpointer_delegate.h b/src/cobalt/speech/endpointer_delegate.h new file mode 100644 index 0000000..db6a528 --- /dev/null +++ b/src/cobalt/speech/endpointer_delegate.h
@@ -0,0 +1,59 @@ +/* + * Copyright 2016 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_SPEECH_ENDPOINTER_DELEGATE_H_ +#define COBALT_SPEECH_ENDPOINTER_DELEGATE_H_ + +#include "content/browser/speech/endpointer/endpointer.h" +#include "media/base/audio_bus.h" + +namespace cobalt { +namespace speech { + +// Delegate of endpointer for detecting the first time sound started in one +// speech session (from start speaking to end of speaking). +class EndPointerDelegate { + public: + typedef ::media::ShellAudioBus ShellAudioBus; + + explicit EndPointerDelegate(int sample_rate); + ~EndPointerDelegate(); + + // Start endpointer session for sound processing. + void Start(); + // Stop endpointer session. + void Stop(); + + // Return true if it is the first time that the sound started. + bool IsFirstTimeSoundStarted(const ShellAudioBus& audio_bus); + + private: + // Used for detecting sound start event. + content::Endpointer endpointer_; + // Used for recording the number of samples before notifying that it is the + // first time the sound started. The |endpointer_| should be started and run + // in the EnvironmentEstimation mode if used in noisy conditions for at least + // 200ms before switching to UserInputMode. + int num_samples_recorded_; + + // Record if it is the first time that the sound started. + bool is_first_time_sound_started_; +}; + +} // namespace speech +} // namespace cobalt + +#endif // COBALT_SPEECH_ENDPOINTER_DELEGATE_H_
diff --git a/src/cobalt/speech/google_streaming_api.pb.cc b/src/cobalt/speech/google_streaming_api.pb.cc new file mode 100644 index 0000000..1d33d80 --- /dev/null +++ b/src/cobalt/speech/google_streaming_api.pb.cc
@@ -0,0 +1,757 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! + +#define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION + +#include <algorithm> + +#include "google_streaming_api.pb.h" + +#include <google/protobuf/stubs/once.h> +#include <google/protobuf/io/coded_stream.h> +#include <google/protobuf/wire_format_lite_inl.h> +// @@protoc_insertion_point(includes) + +namespace cobalt { +namespace speech { +namespace proto { + +void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto() { + delete SpeechRecognitionEvent::default_instance_; + delete SpeechRecognitionResult::default_instance_; + delete SpeechRecognitionAlternative::default_instance_; +} + +void protobuf_AddDesc_google_5fstreaming_5fapi_2eproto() { + static bool already_here = false; + if (already_here) return; + already_here = true; + GOOGLE_PROTOBUF_VERIFY_VERSION; + + SpeechRecognitionEvent::default_instance_ = new SpeechRecognitionEvent(); + SpeechRecognitionResult::default_instance_ = new SpeechRecognitionResult(); + SpeechRecognitionAlternative::default_instance_ = new SpeechRecognitionAlternative(); + SpeechRecognitionEvent::default_instance_->InitAsDefaultInstance(); + SpeechRecognitionResult::default_instance_->InitAsDefaultInstance(); + SpeechRecognitionAlternative::default_instance_->InitAsDefaultInstance(); + ::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto); +} + +// Force AddDescriptors() to be called at static initialization time. +struct StaticDescriptorInitializer_google_5fstreaming_5fapi_2eproto { + StaticDescriptorInitializer_google_5fstreaming_5fapi_2eproto() { + protobuf_AddDesc_google_5fstreaming_5fapi_2eproto(); + } +} static_descriptor_initializer_google_5fstreaming_5fapi_2eproto_; + + +// =================================================================== + +bool SpeechRecognitionEvent_StatusCode_IsValid(int value) { + switch(value) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return true; + default: + return false; + } +} + +#ifndef _MSC_VER +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_SUCCESS; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_NO_SPEECH; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_ABORTED; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_AUDIO_CAPTURE; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_NETWORK; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_NOT_ALLOWED; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_SERVICE_NOT_ALLOWED; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_BAD_GRAMMAR; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::STATUS_LANGUAGE_NOT_SUPPORTED; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::StatusCode_MIN; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::StatusCode_MAX; +const int SpeechRecognitionEvent::StatusCode_ARRAYSIZE; +#endif // _MSC_VER +bool SpeechRecognitionEvent_EndpointerEventType_IsValid(int value) { + switch(value) { + case 0: + case 1: + case 2: + case 3: + return true; + default: + return false; + } +} + +#ifndef _MSC_VER +const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::START_OF_SPEECH; +const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::END_OF_SPEECH; +const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::END_OF_AUDIO; +const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::END_OF_UTTERANCE; +const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::EndpointerEventType_MIN; +const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::EndpointerEventType_MAX; +const int SpeechRecognitionEvent::EndpointerEventType_ARRAYSIZE; +#endif // _MSC_VER +#ifndef _MSC_VER +const int SpeechRecognitionEvent::kStatusFieldNumber; +const int SpeechRecognitionEvent::kResultFieldNumber; +const int SpeechRecognitionEvent::kEndpointFieldNumber; +#endif // !_MSC_VER + +SpeechRecognitionEvent::SpeechRecognitionEvent() + : ::google::protobuf::MessageLite() { + SharedCtor(); +} + +void SpeechRecognitionEvent::InitAsDefaultInstance() { +} + +SpeechRecognitionEvent::SpeechRecognitionEvent(const SpeechRecognitionEvent& from) + : ::google::protobuf::MessageLite() { + SharedCtor(); + MergeFrom(from); +} + +void SpeechRecognitionEvent::SharedCtor() { + _cached_size_ = 0; + status_ = 0; + endpoint_ = 0; + ::memset(_has_bits_, 0, sizeof(_has_bits_)); +} + +SpeechRecognitionEvent::~SpeechRecognitionEvent() { + SharedDtor(); +} + +void SpeechRecognitionEvent::SharedDtor() { + if (this != default_instance_) { + } +} + +void SpeechRecognitionEvent::SetCachedSize(int size) const { + GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); + _cached_size_ = size; + GOOGLE_SAFE_CONCURRENT_WRITES_END(); +} +const SpeechRecognitionEvent& SpeechRecognitionEvent::default_instance() { + if (default_instance_ == NULL) protobuf_AddDesc_google_5fstreaming_5fapi_2eproto(); return *default_instance_; +} + +SpeechRecognitionEvent* SpeechRecognitionEvent::default_instance_ = NULL; + +SpeechRecognitionEvent* SpeechRecognitionEvent::New() const { + return new SpeechRecognitionEvent; +} + +void SpeechRecognitionEvent::Clear() { + if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) { + status_ = 0; + endpoint_ = 0; + } + result_.Clear(); + ::memset(_has_bits_, 0, sizeof(_has_bits_)); +} + +bool SpeechRecognitionEvent::MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) { +#define DO_(EXPRESSION) if (!(EXPRESSION)) return false + ::google::protobuf::uint32 tag; + while ((tag = input->ReadTag()) != 0) { + switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) { + // optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS]; + case 1: { + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) { + int value; + DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< + int, ::google::protobuf::internal::WireFormatLite::TYPE_ENUM>( + input, &value))); + if (::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode_IsValid(value)) { + set_status(static_cast< ::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode >(value)); + } + } else { + goto handle_uninterpreted; + } + if (input->ExpectTag(18)) goto parse_result; + break; + } + + // repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2; + case 2: { + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED) { + parse_result: + DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual( + input, add_result())); + } else { + goto handle_uninterpreted; + } + if (input->ExpectTag(18)) goto parse_result; + if (input->ExpectTag(32)) goto parse_endpoint; + break; + } + + // optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4; + case 4: { + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) { + parse_endpoint: + int value; + DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< + int, ::google::protobuf::internal::WireFormatLite::TYPE_ENUM>( + input, &value))); + if (::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType_IsValid(value)) { + set_endpoint(static_cast< ::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType >(value)); + } + } else { + goto handle_uninterpreted; + } + if (input->ExpectAtEnd()) return true; + break; + } + + default: { + handle_uninterpreted: + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) { + return true; + } + DO_(::google::protobuf::internal::WireFormatLite::SkipField(input, tag)); + break; + } + } + } + return true; +#undef DO_ +} + +void SpeechRecognitionEvent::SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const { + // optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS]; + if (has_status()) { + ::google::protobuf::internal::WireFormatLite::WriteEnum( + 1, this->status(), output); + } + + // repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2; + for (int i = 0; i < this->result_size(); i++) { + ::google::protobuf::internal::WireFormatLite::WriteMessage( + 2, this->result(i), output); + } + + // optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4; + if (has_endpoint()) { + ::google::protobuf::internal::WireFormatLite::WriteEnum( + 4, this->endpoint(), output); + } + +} + +int SpeechRecognitionEvent::ByteSize() const { + int total_size = 0; + + if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) { + // optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS]; + if (has_status()) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::EnumSize(this->status()); + } + + // optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4; + if (has_endpoint()) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::EnumSize(this->endpoint()); + } + + } + // repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2; + total_size += 1 * this->result_size(); + for (int i = 0; i < this->result_size(); i++) { + total_size += + ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual( + this->result(i)); + } + + GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); + _cached_size_ = total_size; + GOOGLE_SAFE_CONCURRENT_WRITES_END(); + return total_size; +} + +void SpeechRecognitionEvent::CheckTypeAndMergeFrom( + const ::google::protobuf::MessageLite& from) { + MergeFrom(*::google::protobuf::down_cast<const SpeechRecognitionEvent*>(&from)); +} + +void SpeechRecognitionEvent::MergeFrom(const SpeechRecognitionEvent& from) { + GOOGLE_CHECK_NE(&from, this); + result_.MergeFrom(from.result_); + if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) { + if (from.has_status()) { + set_status(from.status()); + } + if (from.has_endpoint()) { + set_endpoint(from.endpoint()); + } + } +} + +void SpeechRecognitionEvent::CopyFrom(const SpeechRecognitionEvent& from) { + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool SpeechRecognitionEvent::IsInitialized() const { + + return true; +} + +void SpeechRecognitionEvent::Swap(SpeechRecognitionEvent* other) { + if (other != this) { + std::swap(status_, other->status_); + result_.Swap(&other->result_); + std::swap(endpoint_, other->endpoint_); + std::swap(_has_bits_[0], other->_has_bits_[0]); + std::swap(_cached_size_, other->_cached_size_); + } +} + +::std::string SpeechRecognitionEvent::GetTypeName() const { + return "cobalt.speech.proto.SpeechRecognitionEvent"; +} + + +// =================================================================== + +#ifndef _MSC_VER +const int SpeechRecognitionResult::kAlternativeFieldNumber; +const int SpeechRecognitionResult::kFinalFieldNumber; +const int SpeechRecognitionResult::kStabilityFieldNumber; +#endif // !_MSC_VER + +SpeechRecognitionResult::SpeechRecognitionResult() + : ::google::protobuf::MessageLite() { + SharedCtor(); +} + +void SpeechRecognitionResult::InitAsDefaultInstance() { +} + +SpeechRecognitionResult::SpeechRecognitionResult(const SpeechRecognitionResult& from) + : ::google::protobuf::MessageLite() { + SharedCtor(); + MergeFrom(from); +} + +void SpeechRecognitionResult::SharedCtor() { + _cached_size_ = 0; + final_ = false; + stability_ = 0; + ::memset(_has_bits_, 0, sizeof(_has_bits_)); +} + +SpeechRecognitionResult::~SpeechRecognitionResult() { + SharedDtor(); +} + +void SpeechRecognitionResult::SharedDtor() { + if (this != default_instance_) { + } +} + +void SpeechRecognitionResult::SetCachedSize(int size) const { + GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); + _cached_size_ = size; + GOOGLE_SAFE_CONCURRENT_WRITES_END(); +} +const SpeechRecognitionResult& SpeechRecognitionResult::default_instance() { + if (default_instance_ == NULL) protobuf_AddDesc_google_5fstreaming_5fapi_2eproto(); return *default_instance_; +} + +SpeechRecognitionResult* SpeechRecognitionResult::default_instance_ = NULL; + +SpeechRecognitionResult* SpeechRecognitionResult::New() const { + return new SpeechRecognitionResult; +} + +void SpeechRecognitionResult::Clear() { + if (_has_bits_[1 / 32] & (0xffu << (1 % 32))) { + final_ = false; + stability_ = 0; + } + alternative_.Clear(); + ::memset(_has_bits_, 0, sizeof(_has_bits_)); +} + +bool SpeechRecognitionResult::MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) { +#define DO_(EXPRESSION) if (!(EXPRESSION)) return false + ::google::protobuf::uint32 tag; + while ((tag = input->ReadTag()) != 0) { + switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) { + // repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1; + case 1: { + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED) { + parse_alternative: + DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual( + input, add_alternative())); + } else { + goto handle_uninterpreted; + } + if (input->ExpectTag(10)) goto parse_alternative; + if (input->ExpectTag(16)) goto parse_final; + break; + } + + // optional bool final = 2 [default = false]; + case 2: { + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) { + parse_final: + DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< + bool, ::google::protobuf::internal::WireFormatLite::TYPE_BOOL>( + input, &final_))); + set_has_final(); + } else { + goto handle_uninterpreted; + } + if (input->ExpectTag(29)) goto parse_stability; + break; + } + + // optional float stability = 3; + case 3: { + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_FIXED32) { + parse_stability: + DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< + float, ::google::protobuf::internal::WireFormatLite::TYPE_FLOAT>( + input, &stability_))); + set_has_stability(); + } else { + goto handle_uninterpreted; + } + if (input->ExpectAtEnd()) return true; + break; + } + + default: { + handle_uninterpreted: + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) { + return true; + } + DO_(::google::protobuf::internal::WireFormatLite::SkipField(input, tag)); + break; + } + } + } + return true; +#undef DO_ +} + +void SpeechRecognitionResult::SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const { + // repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1; + for (int i = 0; i < this->alternative_size(); i++) { + ::google::protobuf::internal::WireFormatLite::WriteMessage( + 1, this->alternative(i), output); + } + + // optional bool final = 2 [default = false]; + if (has_final()) { + ::google::protobuf::internal::WireFormatLite::WriteBool(2, this->final(), output); + } + + // optional float stability = 3; + if (has_stability()) { + ::google::protobuf::internal::WireFormatLite::WriteFloat(3, this->stability(), output); + } + +} + +int SpeechRecognitionResult::ByteSize() const { + int total_size = 0; + + if (_has_bits_[1 / 32] & (0xffu << (1 % 32))) { + // optional bool final = 2 [default = false]; + if (has_final()) { + total_size += 1 + 1; + } + + // optional float stability = 3; + if (has_stability()) { + total_size += 1 + 4; + } + + } + // repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1; + total_size += 1 * this->alternative_size(); + for (int i = 0; i < this->alternative_size(); i++) { + total_size += + ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual( + this->alternative(i)); + } + + GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); + _cached_size_ = total_size; + GOOGLE_SAFE_CONCURRENT_WRITES_END(); + return total_size; +} + +void SpeechRecognitionResult::CheckTypeAndMergeFrom( + const ::google::protobuf::MessageLite& from) { + MergeFrom(*::google::protobuf::down_cast<const SpeechRecognitionResult*>(&from)); +} + +void SpeechRecognitionResult::MergeFrom(const SpeechRecognitionResult& from) { + GOOGLE_CHECK_NE(&from, this); + alternative_.MergeFrom(from.alternative_); + if (from._has_bits_[1 / 32] & (0xffu << (1 % 32))) { + if (from.has_final()) { + set_final(from.final()); + } + if (from.has_stability()) { + set_stability(from.stability()); + } + } +} + +void SpeechRecognitionResult::CopyFrom(const SpeechRecognitionResult& from) { + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool SpeechRecognitionResult::IsInitialized() const { + + return true; +} + +void SpeechRecognitionResult::Swap(SpeechRecognitionResult* other) { + if (other != this) { + alternative_.Swap(&other->alternative_); + std::swap(final_, other->final_); + std::swap(stability_, other->stability_); + std::swap(_has_bits_[0], other->_has_bits_[0]); + std::swap(_cached_size_, other->_cached_size_); + } +} + +::std::string SpeechRecognitionResult::GetTypeName() const { + return "cobalt.speech.proto.SpeechRecognitionResult"; +} + + +// =================================================================== + +#ifndef _MSC_VER +const int SpeechRecognitionAlternative::kTranscriptFieldNumber; +const int SpeechRecognitionAlternative::kConfidenceFieldNumber; +#endif // !_MSC_VER + +SpeechRecognitionAlternative::SpeechRecognitionAlternative() + : ::google::protobuf::MessageLite() { + SharedCtor(); +} + +void SpeechRecognitionAlternative::InitAsDefaultInstance() { +} + +SpeechRecognitionAlternative::SpeechRecognitionAlternative(const SpeechRecognitionAlternative& from) + : ::google::protobuf::MessageLite() { + SharedCtor(); + MergeFrom(from); +} + +void SpeechRecognitionAlternative::SharedCtor() { + _cached_size_ = 0; + transcript_ = const_cast< ::std::string*>(&::google::protobuf::internal::kEmptyString); + confidence_ = 0; + ::memset(_has_bits_, 0, sizeof(_has_bits_)); +} + +SpeechRecognitionAlternative::~SpeechRecognitionAlternative() { + SharedDtor(); +} + +void SpeechRecognitionAlternative::SharedDtor() { + if (transcript_ != &::google::protobuf::internal::kEmptyString) { + delete transcript_; + } + if (this != default_instance_) { + } +} + +void SpeechRecognitionAlternative::SetCachedSize(int size) const { + GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); + _cached_size_ = size; + GOOGLE_SAFE_CONCURRENT_WRITES_END(); +} +const SpeechRecognitionAlternative& SpeechRecognitionAlternative::default_instance() { + if (default_instance_ == NULL) protobuf_AddDesc_google_5fstreaming_5fapi_2eproto(); return *default_instance_; +} + +SpeechRecognitionAlternative* SpeechRecognitionAlternative::default_instance_ = NULL; + +SpeechRecognitionAlternative* SpeechRecognitionAlternative::New() const { + return new SpeechRecognitionAlternative; +} + +void SpeechRecognitionAlternative::Clear() { + if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) { + if (has_transcript()) { + if (transcript_ != &::google::protobuf::internal::kEmptyString) { + transcript_->clear(); + } + } + confidence_ = 0; + } + ::memset(_has_bits_, 0, sizeof(_has_bits_)); +} + +bool SpeechRecognitionAlternative::MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) { +#define DO_(EXPRESSION) if (!(EXPRESSION)) return false + ::google::protobuf::uint32 tag; + while ((tag = input->ReadTag()) != 0) { + switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) { + // optional string transcript = 1; + case 1: { + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_LENGTH_DELIMITED) { + DO_(::google::protobuf::internal::WireFormatLite::ReadString( + input, this->mutable_transcript())); + } else { + goto handle_uninterpreted; + } + if (input->ExpectTag(21)) goto parse_confidence; + break; + } + + // optional float confidence = 2; + case 2: { + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_FIXED32) { + parse_confidence: + DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< + float, ::google::protobuf::internal::WireFormatLite::TYPE_FLOAT>( + input, &confidence_))); + set_has_confidence(); + } else { + goto handle_uninterpreted; + } + if (input->ExpectAtEnd()) return true; + break; + } + + default: { + handle_uninterpreted: + if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) == + ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) { + return true; + } + DO_(::google::protobuf::internal::WireFormatLite::SkipField(input, tag)); + break; + } + } + } + return true; +#undef DO_ +} + +void SpeechRecognitionAlternative::SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const { + // optional string transcript = 1; + if (has_transcript()) { + ::google::protobuf::internal::WireFormatLite::WriteString( + 1, this->transcript(), output); + } + + // optional float confidence = 2; + if (has_confidence()) { + ::google::protobuf::internal::WireFormatLite::WriteFloat(2, this->confidence(), output); + } + +} + +int SpeechRecognitionAlternative::ByteSize() const { + int total_size = 0; + + if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) { + // optional string transcript = 1; + if (has_transcript()) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::StringSize( + this->transcript()); + } + + // optional float confidence = 2; + if (has_confidence()) { + total_size += 1 + 4; + } + + } + GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); + _cached_size_ = total_size; + GOOGLE_SAFE_CONCURRENT_WRITES_END(); + return total_size; +} + +void SpeechRecognitionAlternative::CheckTypeAndMergeFrom( + const ::google::protobuf::MessageLite& from) { + MergeFrom(*::google::protobuf::down_cast<const SpeechRecognitionAlternative*>(&from)); +} + +void SpeechRecognitionAlternative::MergeFrom(const SpeechRecognitionAlternative& from) { + GOOGLE_CHECK_NE(&from, this); + if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) { + if (from.has_transcript()) { + set_transcript(from.transcript()); + } + if (from.has_confidence()) { + set_confidence(from.confidence()); + } + } +} + +void SpeechRecognitionAlternative::CopyFrom(const SpeechRecognitionAlternative& from) { + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool SpeechRecognitionAlternative::IsInitialized() const { + + return true; +} + +void SpeechRecognitionAlternative::Swap(SpeechRecognitionAlternative* other) { + if (other != this) { + std::swap(transcript_, other->transcript_); + std::swap(confidence_, other->confidence_); + std::swap(_has_bits_[0], other->_has_bits_[0]); + std::swap(_cached_size_, other->_cached_size_); + } +} + +::std::string SpeechRecognitionAlternative::GetTypeName() const { + return "cobalt.speech.proto.SpeechRecognitionAlternative"; +} + + +// @@protoc_insertion_point(namespace_scope) + +} // namespace proto +} // namespace speech +} // namespace cobalt + +// @@protoc_insertion_point(global_scope)
diff --git a/src/cobalt/speech/google_streaming_api.pb.h b/src/cobalt/speech/google_streaming_api.pb.h new file mode 100644 index 0000000..b666423 --- /dev/null +++ b/src/cobalt/speech/google_streaming_api.pb.h
@@ -0,0 +1,613 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: google_streaming_api.proto + +#ifndef PROTOBUF_google_5fstreaming_5fapi_2eproto__INCLUDED +#define PROTOBUF_google_5fstreaming_5fapi_2eproto__INCLUDED + +#include <string> + +#include <google/protobuf/stubs/common.h> + +#if GOOGLE_PROTOBUF_VERSION < 2004000 +#error This file was generated by a newer version of protoc which is +#error incompatible with your Protocol Buffer headers. Please update +#error your headers. +#endif +#if 2004002 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION +#error This file was generated by an older version of protoc which is +#error incompatible with your Protocol Buffer headers. Please +#error regenerate this file with a newer version of protoc. +#endif + +#include <google/protobuf/generated_message_util.h> +#include <google/protobuf/repeated_field.h> +#include <google/protobuf/extension_set.h> +// @@protoc_insertion_point(includes) + +namespace cobalt { +namespace speech { +namespace proto { + +// Internal implementation detail -- do not call these. +void protobuf_AddDesc_google_5fstreaming_5fapi_2eproto(); +void protobuf_AssignDesc_google_5fstreaming_5fapi_2eproto(); +void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto(); + +class SpeechRecognitionEvent; +class SpeechRecognitionResult; +class SpeechRecognitionAlternative; + +enum SpeechRecognitionEvent_StatusCode { + SpeechRecognitionEvent_StatusCode_STATUS_SUCCESS = 0, + SpeechRecognitionEvent_StatusCode_STATUS_NO_SPEECH = 1, + SpeechRecognitionEvent_StatusCode_STATUS_ABORTED = 2, + SpeechRecognitionEvent_StatusCode_STATUS_AUDIO_CAPTURE = 3, + SpeechRecognitionEvent_StatusCode_STATUS_NETWORK = 4, + SpeechRecognitionEvent_StatusCode_STATUS_NOT_ALLOWED = 5, + SpeechRecognitionEvent_StatusCode_STATUS_SERVICE_NOT_ALLOWED = 6, + SpeechRecognitionEvent_StatusCode_STATUS_BAD_GRAMMAR = 7, + SpeechRecognitionEvent_StatusCode_STATUS_LANGUAGE_NOT_SUPPORTED = 8 +}; +bool SpeechRecognitionEvent_StatusCode_IsValid(int value); +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent_StatusCode_StatusCode_MIN = SpeechRecognitionEvent_StatusCode_STATUS_SUCCESS; +const SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent_StatusCode_StatusCode_MAX = SpeechRecognitionEvent_StatusCode_STATUS_LANGUAGE_NOT_SUPPORTED; +const int SpeechRecognitionEvent_StatusCode_StatusCode_ARRAYSIZE = SpeechRecognitionEvent_StatusCode_StatusCode_MAX + 1; + +enum SpeechRecognitionEvent_EndpointerEventType { + SpeechRecognitionEvent_EndpointerEventType_START_OF_SPEECH = 0, + SpeechRecognitionEvent_EndpointerEventType_END_OF_SPEECH = 1, + SpeechRecognitionEvent_EndpointerEventType_END_OF_AUDIO = 2, + SpeechRecognitionEvent_EndpointerEventType_END_OF_UTTERANCE = 3 +}; +bool SpeechRecognitionEvent_EndpointerEventType_IsValid(int value); +const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MIN = SpeechRecognitionEvent_EndpointerEventType_START_OF_SPEECH; +const SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MAX = SpeechRecognitionEvent_EndpointerEventType_END_OF_UTTERANCE; +const int SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_ARRAYSIZE = SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MAX + 1; + +// =================================================================== + +class SpeechRecognitionEvent : public ::google::protobuf::MessageLite { + public: + SpeechRecognitionEvent(); + virtual ~SpeechRecognitionEvent(); + + SpeechRecognitionEvent(const SpeechRecognitionEvent& from); + + inline SpeechRecognitionEvent& operator=(const SpeechRecognitionEvent& from) { + CopyFrom(from); + return *this; + } + + static const SpeechRecognitionEvent& default_instance(); + + void Swap(SpeechRecognitionEvent* other); + + // implements Message ---------------------------------------------- + + SpeechRecognitionEvent* New() const; + void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from); + void CopyFrom(const SpeechRecognitionEvent& from); + void MergeFrom(const SpeechRecognitionEvent& from); + void Clear(); + bool IsInitialized() const; + + int ByteSize() const; + bool MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input); + void SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const; + int GetCachedSize() const { return _cached_size_; } + private: + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const; + public: + + ::std::string GetTypeName() const; + + // nested types ---------------------------------------------------- + + typedef SpeechRecognitionEvent_StatusCode StatusCode; + static const StatusCode STATUS_SUCCESS = SpeechRecognitionEvent_StatusCode_STATUS_SUCCESS; + static const StatusCode STATUS_NO_SPEECH = SpeechRecognitionEvent_StatusCode_STATUS_NO_SPEECH; + static const StatusCode STATUS_ABORTED = SpeechRecognitionEvent_StatusCode_STATUS_ABORTED; + static const StatusCode STATUS_AUDIO_CAPTURE = SpeechRecognitionEvent_StatusCode_STATUS_AUDIO_CAPTURE; + static const StatusCode STATUS_NETWORK = SpeechRecognitionEvent_StatusCode_STATUS_NETWORK; + static const StatusCode STATUS_NOT_ALLOWED = SpeechRecognitionEvent_StatusCode_STATUS_NOT_ALLOWED; + static const StatusCode STATUS_SERVICE_NOT_ALLOWED = SpeechRecognitionEvent_StatusCode_STATUS_SERVICE_NOT_ALLOWED; + static const StatusCode STATUS_BAD_GRAMMAR = SpeechRecognitionEvent_StatusCode_STATUS_BAD_GRAMMAR; + static const StatusCode STATUS_LANGUAGE_NOT_SUPPORTED = SpeechRecognitionEvent_StatusCode_STATUS_LANGUAGE_NOT_SUPPORTED; + static inline bool StatusCode_IsValid(int value) { + return SpeechRecognitionEvent_StatusCode_IsValid(value); + } + static const StatusCode StatusCode_MIN = + SpeechRecognitionEvent_StatusCode_StatusCode_MIN; + static const StatusCode StatusCode_MAX = + SpeechRecognitionEvent_StatusCode_StatusCode_MAX; + static const int StatusCode_ARRAYSIZE = + SpeechRecognitionEvent_StatusCode_StatusCode_ARRAYSIZE; + + typedef SpeechRecognitionEvent_EndpointerEventType EndpointerEventType; + static const EndpointerEventType START_OF_SPEECH = SpeechRecognitionEvent_EndpointerEventType_START_OF_SPEECH; + static const EndpointerEventType END_OF_SPEECH = SpeechRecognitionEvent_EndpointerEventType_END_OF_SPEECH; + static const EndpointerEventType END_OF_AUDIO = SpeechRecognitionEvent_EndpointerEventType_END_OF_AUDIO; + static const EndpointerEventType END_OF_UTTERANCE = SpeechRecognitionEvent_EndpointerEventType_END_OF_UTTERANCE; + static inline bool EndpointerEventType_IsValid(int value) { + return SpeechRecognitionEvent_EndpointerEventType_IsValid(value); + } + static const EndpointerEventType EndpointerEventType_MIN = + SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MIN; + static const EndpointerEventType EndpointerEventType_MAX = + SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_MAX; + static const int EndpointerEventType_ARRAYSIZE = + SpeechRecognitionEvent_EndpointerEventType_EndpointerEventType_ARRAYSIZE; + + // accessors ------------------------------------------------------- + + // optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS]; + inline bool has_status() const; + inline void clear_status(); + static const int kStatusFieldNumber = 1; + inline ::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode status() const; + inline void set_status(::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode value); + + // repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2; + inline int result_size() const; + inline void clear_result(); + static const int kResultFieldNumber = 2; + inline const ::cobalt::speech::proto::SpeechRecognitionResult& result(int index) const; + inline ::cobalt::speech::proto::SpeechRecognitionResult* mutable_result(int index); + inline ::cobalt::speech::proto::SpeechRecognitionResult* add_result(); + inline const ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult >& + result() const; + inline ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult >* + mutable_result(); + + // optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4; + inline bool has_endpoint() const; + inline void clear_endpoint(); + static const int kEndpointFieldNumber = 4; + inline ::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType endpoint() const; + inline void set_endpoint(::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType value); + + // @@protoc_insertion_point(class_scope:cobalt.speech.proto.SpeechRecognitionEvent) + private: + inline void set_has_status(); + inline void clear_has_status(); + inline void set_has_endpoint(); + inline void clear_has_endpoint(); + + ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult > result_; + int status_; + int endpoint_; + + mutable int _cached_size_; + ::google::protobuf::uint32 _has_bits_[(3 + 31) / 32]; + + friend void protobuf_AddDesc_google_5fstreaming_5fapi_2eproto(); + friend void protobuf_AssignDesc_google_5fstreaming_5fapi_2eproto(); + friend void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto(); + + void InitAsDefaultInstance(); + static SpeechRecognitionEvent* default_instance_; +}; +// ------------------------------------------------------------------- + +class SpeechRecognitionResult : public ::google::protobuf::MessageLite { + public: + SpeechRecognitionResult(); + virtual ~SpeechRecognitionResult(); + + SpeechRecognitionResult(const SpeechRecognitionResult& from); + + inline SpeechRecognitionResult& operator=(const SpeechRecognitionResult& from) { + CopyFrom(from); + return *this; + } + + static const SpeechRecognitionResult& default_instance(); + + void Swap(SpeechRecognitionResult* other); + + // implements Message ---------------------------------------------- + + SpeechRecognitionResult* New() const; + void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from); + void CopyFrom(const SpeechRecognitionResult& from); + void MergeFrom(const SpeechRecognitionResult& from); + void Clear(); + bool IsInitialized() const; + + int ByteSize() const; + bool MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input); + void SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const; + int GetCachedSize() const { return _cached_size_; } + private: + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const; + public: + + ::std::string GetTypeName() const; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + // repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1; + inline int alternative_size() const; + inline void clear_alternative(); + static const int kAlternativeFieldNumber = 1; + inline const ::cobalt::speech::proto::SpeechRecognitionAlternative& alternative(int index) const; + inline ::cobalt::speech::proto::SpeechRecognitionAlternative* mutable_alternative(int index); + inline ::cobalt::speech::proto::SpeechRecognitionAlternative* add_alternative(); + inline const ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative >& + alternative() const; + inline ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative >* + mutable_alternative(); + + // optional bool final = 2 [default = false]; + inline bool has_final() const; + inline void clear_final(); + static const int kFinalFieldNumber = 2; + inline bool final() const; + inline void set_final(bool value); + + // optional float stability = 3; + inline bool has_stability() const; + inline void clear_stability(); + static const int kStabilityFieldNumber = 3; + inline float stability() const; + inline void set_stability(float value); + + // @@protoc_insertion_point(class_scope:cobalt.speech.proto.SpeechRecognitionResult) + private: + inline void set_has_final(); + inline void clear_has_final(); + inline void set_has_stability(); + inline void clear_has_stability(); + + ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative > alternative_; + bool final_; + float stability_; + + mutable int _cached_size_; + ::google::protobuf::uint32 _has_bits_[(3 + 31) / 32]; + + friend void protobuf_AddDesc_google_5fstreaming_5fapi_2eproto(); + friend void protobuf_AssignDesc_google_5fstreaming_5fapi_2eproto(); + friend void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto(); + + void InitAsDefaultInstance(); + static SpeechRecognitionResult* default_instance_; +}; +// ------------------------------------------------------------------- + +class SpeechRecognitionAlternative : public ::google::protobuf::MessageLite { + public: + SpeechRecognitionAlternative(); + virtual ~SpeechRecognitionAlternative(); + + SpeechRecognitionAlternative(const SpeechRecognitionAlternative& from); + + inline SpeechRecognitionAlternative& operator=(const SpeechRecognitionAlternative& from) { + CopyFrom(from); + return *this; + } + + static const SpeechRecognitionAlternative& default_instance(); + + void Swap(SpeechRecognitionAlternative* other); + + // implements Message ---------------------------------------------- + + SpeechRecognitionAlternative* New() const; + void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from); + void CopyFrom(const SpeechRecognitionAlternative& from); + void MergeFrom(const SpeechRecognitionAlternative& from); + void Clear(); + bool IsInitialized() const; + + int ByteSize() const; + bool MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input); + void SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const; + int GetCachedSize() const { return _cached_size_; } + private: + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const; + public: + + ::std::string GetTypeName() const; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + // optional string transcript = 1; + inline bool has_transcript() const; + inline void clear_transcript(); + static const int kTranscriptFieldNumber = 1; + inline const ::std::string& transcript() const; + inline void set_transcript(const ::std::string& value); + inline void set_transcript(const char* value); + inline void set_transcript(const char* value, size_t size); + inline ::std::string* mutable_transcript(); + inline ::std::string* release_transcript(); + + // optional float confidence = 2; + inline bool has_confidence() const; + inline void clear_confidence(); + static const int kConfidenceFieldNumber = 2; + inline float confidence() const; + inline void set_confidence(float value); + + // @@protoc_insertion_point(class_scope:cobalt.speech.proto.SpeechRecognitionAlternative) + private: + inline void set_has_transcript(); + inline void clear_has_transcript(); + inline void set_has_confidence(); + inline void clear_has_confidence(); + + ::std::string* transcript_; + float confidence_; + + mutable int _cached_size_; + ::google::protobuf::uint32 _has_bits_[(2 + 31) / 32]; + + friend void protobuf_AddDesc_google_5fstreaming_5fapi_2eproto(); + friend void protobuf_AssignDesc_google_5fstreaming_5fapi_2eproto(); + friend void protobuf_ShutdownFile_google_5fstreaming_5fapi_2eproto(); + + void InitAsDefaultInstance(); + static SpeechRecognitionAlternative* default_instance_; +}; +// =================================================================== + + +// =================================================================== + +// SpeechRecognitionEvent + +// optional .cobalt.speech.proto.SpeechRecognitionEvent.StatusCode status = 1 [default = STATUS_SUCCESS]; +inline bool SpeechRecognitionEvent::has_status() const { + return (_has_bits_[0] & 0x00000001u) != 0; +} +inline void SpeechRecognitionEvent::set_has_status() { + _has_bits_[0] |= 0x00000001u; +} +inline void SpeechRecognitionEvent::clear_has_status() { + _has_bits_[0] &= ~0x00000001u; +} +inline void SpeechRecognitionEvent::clear_status() { + status_ = 0; + clear_has_status(); +} +inline ::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode SpeechRecognitionEvent::status() const { + return static_cast< ::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode >(status_); +} +inline void SpeechRecognitionEvent::set_status(::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode value) { + GOOGLE_DCHECK(::cobalt::speech::proto::SpeechRecognitionEvent_StatusCode_IsValid(value)); + set_has_status(); + status_ = value; +} + +// repeated .cobalt.speech.proto.SpeechRecognitionResult result = 2; +inline int SpeechRecognitionEvent::result_size() const { + return result_.size(); +} +inline void SpeechRecognitionEvent::clear_result() { + result_.Clear(); +} +inline const ::cobalt::speech::proto::SpeechRecognitionResult& SpeechRecognitionEvent::result(int index) const { + return result_.Get(index); +} +inline ::cobalt::speech::proto::SpeechRecognitionResult* SpeechRecognitionEvent::mutable_result(int index) { + return result_.Mutable(index); +} +inline ::cobalt::speech::proto::SpeechRecognitionResult* SpeechRecognitionEvent::add_result() { + return result_.Add(); +} +inline const ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult >& +SpeechRecognitionEvent::result() const { + return result_; +} +inline ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionResult >* +SpeechRecognitionEvent::mutable_result() { + return &result_; +} + +// optional .cobalt.speech.proto.SpeechRecognitionEvent.EndpointerEventType endpoint = 4; +inline bool SpeechRecognitionEvent::has_endpoint() const { + return (_has_bits_[0] & 0x00000004u) != 0; +} +inline void SpeechRecognitionEvent::set_has_endpoint() { + _has_bits_[0] |= 0x00000004u; +} +inline void SpeechRecognitionEvent::clear_has_endpoint() { + _has_bits_[0] &= ~0x00000004u; +} +inline void SpeechRecognitionEvent::clear_endpoint() { + endpoint_ = 0; + clear_has_endpoint(); +} +inline ::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType SpeechRecognitionEvent::endpoint() const { + return static_cast< ::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType >(endpoint_); +} +inline void SpeechRecognitionEvent::set_endpoint(::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType value) { + GOOGLE_DCHECK(::cobalt::speech::proto::SpeechRecognitionEvent_EndpointerEventType_IsValid(value)); + set_has_endpoint(); + endpoint_ = value; +} + +// ------------------------------------------------------------------- + +// SpeechRecognitionResult + +// repeated .cobalt.speech.proto.SpeechRecognitionAlternative alternative = 1; +inline int SpeechRecognitionResult::alternative_size() const { + return alternative_.size(); +} +inline void SpeechRecognitionResult::clear_alternative() { + alternative_.Clear(); +} +inline const ::cobalt::speech::proto::SpeechRecognitionAlternative& SpeechRecognitionResult::alternative(int index) const { + return alternative_.Get(index); +} +inline ::cobalt::speech::proto::SpeechRecognitionAlternative* SpeechRecognitionResult::mutable_alternative(int index) { + return alternative_.Mutable(index); +} +inline ::cobalt::speech::proto::SpeechRecognitionAlternative* SpeechRecognitionResult::add_alternative() { + return alternative_.Add(); +} +inline const ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative >& +SpeechRecognitionResult::alternative() const { + return alternative_; +} +inline ::google::protobuf::RepeatedPtrField< ::cobalt::speech::proto::SpeechRecognitionAlternative >* +SpeechRecognitionResult::mutable_alternative() { + return &alternative_; +} + +// optional bool final = 2 [default = false]; +inline bool SpeechRecognitionResult::has_final() const { + return (_has_bits_[0] & 0x00000002u) != 0; +} +inline void SpeechRecognitionResult::set_has_final() { + _has_bits_[0] |= 0x00000002u; +} +inline void SpeechRecognitionResult::clear_has_final() { + _has_bits_[0] &= ~0x00000002u; +} +inline void SpeechRecognitionResult::clear_final() { + final_ = false; + clear_has_final(); +} +inline bool SpeechRecognitionResult::final() const { + return final_; +} +inline void SpeechRecognitionResult::set_final(bool value) { + set_has_final(); + final_ = value; +} + +// optional float stability = 3; +inline bool SpeechRecognitionResult::has_stability() const { + return (_has_bits_[0] & 0x00000004u) != 0; +} +inline void SpeechRecognitionResult::set_has_stability() { + _has_bits_[0] |= 0x00000004u; +} +inline void SpeechRecognitionResult::clear_has_stability() { + _has_bits_[0] &= ~0x00000004u; +} +inline void SpeechRecognitionResult::clear_stability() { + stability_ = 0; + clear_has_stability(); +} +inline float SpeechRecognitionResult::stability() const { + return stability_; +} +inline void SpeechRecognitionResult::set_stability(float value) { + set_has_stability(); + stability_ = value; +} + +// ------------------------------------------------------------------- + +// SpeechRecognitionAlternative + +// optional string transcript = 1; +inline bool SpeechRecognitionAlternative::has_transcript() const { + return (_has_bits_[0] & 0x00000001u) != 0; +} +inline void SpeechRecognitionAlternative::set_has_transcript() { + _has_bits_[0] |= 0x00000001u; +} +inline void SpeechRecognitionAlternative::clear_has_transcript() { + _has_bits_[0] &= ~0x00000001u; +} +inline void SpeechRecognitionAlternative::clear_transcript() { + if (transcript_ != &::google::protobuf::internal::kEmptyString) { + transcript_->clear(); + } + clear_has_transcript(); +} +inline const ::std::string& SpeechRecognitionAlternative::transcript() const { + return *transcript_; +} +inline void SpeechRecognitionAlternative::set_transcript(const ::std::string& value) { + set_has_transcript(); + if (transcript_ == &::google::protobuf::internal::kEmptyString) { + transcript_ = new ::std::string; + } + transcript_->assign(value); +} +inline void SpeechRecognitionAlternative::set_transcript(const char* value) { + set_has_transcript(); + if (transcript_ == &::google::protobuf::internal::kEmptyString) { + transcript_ = new ::std::string; + } + transcript_->assign(value); +} +inline void SpeechRecognitionAlternative::set_transcript(const char* value, size_t size) { + set_has_transcript(); + if (transcript_ == &::google::protobuf::internal::kEmptyString) { + transcript_ = new ::std::string; + } + transcript_->assign(reinterpret_cast<const char*>(value), size); +} +inline ::std::string* SpeechRecognitionAlternative::mutable_transcript() { + set_has_transcript(); + if (transcript_ == &::google::protobuf::internal::kEmptyString) { + transcript_ = new ::std::string; + } + return transcript_; +} +inline ::std::string* SpeechRecognitionAlternative::release_transcript() { + clear_has_transcript(); + if (transcript_ == &::google::protobuf::internal::kEmptyString) { + return NULL; + } else { + ::std::string* temp = transcript_; + transcript_ = const_cast< ::std::string*>(&::google::protobuf::internal::kEmptyString); + return temp; + } +} + +// optional float confidence = 2; +inline bool SpeechRecognitionAlternative::has_confidence() const { + return (_has_bits_[0] & 0x00000002u) != 0; +} +inline void SpeechRecognitionAlternative::set_has_confidence() { + _has_bits_[0] |= 0x00000002u; +} +inline void SpeechRecognitionAlternative::clear_has_confidence() { + _has_bits_[0] &= ~0x00000002u; +} +inline void SpeechRecognitionAlternative::clear_confidence() { + confidence_ = 0; + clear_has_confidence(); +} +inline float SpeechRecognitionAlternative::confidence() const { + return confidence_; +} +inline void SpeechRecognitionAlternative::set_confidence(float value) { + set_has_confidence(); + confidence_ = value; +} + + +// @@protoc_insertion_point(namespace_scope) + +} // namespace proto +} // namespace speech +} // namespace cobalt + +// @@protoc_insertion_point(global_scope) + +#endif // PROTOBUF_google_5fstreaming_5fapi_2eproto__INCLUDED
diff --git a/src/cobalt/speech/google_streaming_api.proto b/src/cobalt/speech/google_streaming_api.proto new file mode 100644 index 0000000..b9b5db8 --- /dev/null +++ b/src/cobalt/speech/google_streaming_api.proto
@@ -0,0 +1,79 @@ +// 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. + +// Command to generate google_streaming_api.pb.h and google_streaming_api.pb.cc +// protoc --proto_path=. --cpp_out=. google_streaming_api.proto + +syntax = "proto2"; +option optimize_for = LITE_RUNTIME; + +// TODO(hans): Commented out due to compilation errors. +// option cc_api_version = 2; + +package cobalt.speech.proto; + +// SpeechRecognitionEvent is the only message type sent to client. +// +// The first SpeechRecognitionEvent is an empty (default) message to indicate +// as early as possible that the stream connection has been established. +message SpeechRecognitionEvent { + enum StatusCode { + // Note: in JavaScript API SpeechRecognitionError 0 is "OTHER" error. + STATUS_SUCCESS = 0; + STATUS_NO_SPEECH = 1; + STATUS_ABORTED = 2; + STATUS_AUDIO_CAPTURE = 3; + STATUS_NETWORK = 4; + STATUS_NOT_ALLOWED = 5; + STATUS_SERVICE_NOT_ALLOWED = 6; + STATUS_BAD_GRAMMAR = 7; + STATUS_LANGUAGE_NOT_SUPPORTED = 8; + } + optional StatusCode status = 1 [default = STATUS_SUCCESS]; + + // May contain zero or one final=true result (the newly settled portion). + // May also contain zero or more final=false results. + // (Note that this differs from JavaScript API resultHistory in that no more + // than one final=true result is returned, so client must accumulate + // resultHistory by concatenating the final=true results.) + repeated SpeechRecognitionResult result = 2; + + enum EndpointerEventType { + START_OF_SPEECH = 0; + END_OF_SPEECH = 1; + END_OF_AUDIO = 2; // End of audio stream has been reached. + // End of utterance indicates that no more speech segments are expected. + END_OF_UTTERANCE = 3; + } + + optional EndpointerEventType endpoint = 4; +}; + +message SpeechRecognitionResult { + repeated SpeechRecognitionAlternative alternative = 1; + + // True if this is the final time the speech service will return this + // particular SpeechRecognitionResult. If false, then this represents an + // interim result that could still be changed. + optional bool final = 2 [default = false]; + + // An estimate of the probability that the recognizer will not change its + // guess about this interim result. Values range from 0.0 (completely + // unstable) to 1.0 (completely stable). Note that this is not the same as + // "confidence", which estimate the probability that a recognition result + // is correct. This field is only provided for interim (final=false) results. + optional float stability = 3; +}; + +// Item in N-best list. +message SpeechRecognitionAlternative { + // Spoken text. + optional string transcript = 1; + + // The confidence estimate between 0.0 and 1.0. A higher number means the + // system is more confident that the recognition is correct. + // This field is typically provided only for the top hypothesis and only for + // final results. + optional float confidence = 2; +}
diff --git a/src/cobalt/speech/mic.h b/src/cobalt/speech/mic.h deleted file mode 100644 index 4eac5e1..0000000 --- a/src/cobalt/speech/mic.h +++ /dev/null
@@ -1,66 +0,0 @@ -/* - * Copyright 2016 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_SPEECH_MIC_H_ -#define COBALT_SPEECH_MIC_H_ - -#include "base/callback.h" -#include "media/base/audio_bus.h" - -namespace cobalt { -namespace speech { - -// An abstract class is used for interacting platform specific microphone. -class Mic { - public: - typedef ::media::AudioBus AudioBus; - typedef base::Callback<void(scoped_ptr<AudioBus>)> DataReceivedCallback; - typedef base::Callback<void(void)> CompletionCallback; - typedef base::Callback<void(void)> ErrorCallback; - - virtual ~Mic() {} - - static scoped_ptr<Mic> Create(int sample_rate, - const DataReceivedCallback& data_received, - const CompletionCallback& completion, - const ErrorCallback& error); - - // Multiple calls to Start/Stop are allowed, the implementation should take - // care of multiple calls. The Start/Stop methods are required to be called in - // the same thread. - // Start microphone to receive voice. - virtual void Start() = 0; - // Stop microphone from receiving voice. - virtual void Stop() = 0; - - protected: - Mic(int sample_rate, const DataReceivedCallback& data_received, - const CompletionCallback& completion, const ErrorCallback& error) - : sample_rate_(sample_rate), - data_received_callback_(data_received), - completion_callback_(completion), - error_callback_(error) {} - - int sample_rate_; - const DataReceivedCallback data_received_callback_; - const CompletionCallback completion_callback_; - const ErrorCallback error_callback_; -}; - -} // namespace speech -} // namespace cobalt - -#endif // COBALT_SPEECH_MIC_H_
diff --git a/src/cobalt/speech/mic_linux.cc b/src/cobalt/speech/mic_linux.cc deleted file mode 100644 index 5621116..0000000 --- a/src/cobalt/speech/mic_linux.cc +++ /dev/null
@@ -1,42 +0,0 @@ -/* - * Copyright 2016 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/speech/mic.h" - -namespace cobalt { -namespace speech { - -class MicLinux : public Mic { - public: - MicLinux(int sample_rate, const DataReceivedCallback& data_received, - const CompletionCallback& completion, const ErrorCallback& error) - : Mic(sample_rate, data_received, completion, error) {} - - void Start() OVERRIDE { NOTIMPLEMENTED(); } - void Stop() OVERRIDE { NOTIMPLEMENTED(); } -}; - -// static -scoped_ptr<Mic> Mic::Create(int sample_rate, - const DataReceivedCallback& data_received, - const CompletionCallback& completion, - const ErrorCallback& error) { - return make_scoped_ptr<Mic>( - new MicLinux(sample_rate, data_received, completion, error)); -} - -} // namespace speech -} // namespace cobalt
diff --git a/src/cobalt/speech/mic_starboard.cc b/src/cobalt/speech/mic_starboard.cc deleted file mode 100644 index 1b164ba..0000000 --- a/src/cobalt/speech/mic_starboard.cc +++ /dev/null
@@ -1,42 +0,0 @@ -/* - * Copyright 2016 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/speech/mic.h" - -namespace cobalt { -namespace speech { - -class MicStarboard : public Mic { - public: - MicStarboard(int sample_rate, const DataReceivedCallback& data_received, - const CompletionCallback& completion, const ErrorCallback& error) - : Mic(sample_rate, data_received, completion, error) {} - - void Start() OVERRIDE { NOTIMPLEMENTED(); } - void Stop() OVERRIDE { NOTIMPLEMENTED(); } -}; - -// static -scoped_ptr<Mic> Mic::Create(int sample_rate, - const DataReceivedCallback& data_received, - const CompletionCallback& completion, - const ErrorCallback& error) { - return make_scoped_ptr<Mic>( - new MicStarboard(sample_rate, data_received, completion, error)); -} - -} // namespace speech -} // namespace cobalt
diff --git a/src/cobalt/speech/mic_win.cc b/src/cobalt/speech/mic_win.cc deleted file mode 100644 index 2025566..0000000 --- a/src/cobalt/speech/mic_win.cc +++ /dev/null
@@ -1,42 +0,0 @@ -/* - * Copyright 2016 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/speech/mic.h" - -namespace cobalt { -namespace speech { - -class MicWin : public Mic { - public: - MicWin(int sample_rate, const DataReceivedCallback& data_received, - const CompletionCallback& completion, const ErrorCallback& error) - : Mic(sample_rate, data_received, completion, error) {} - - void Start() OVERRIDE { NOTIMPLEMENTED(); } - void Stop() OVERRIDE { NOTIMPLEMENTED(); } -}; - -// static -scoped_ptr<Mic> Mic::Create(int sample_rate, - const DataReceivedCallback& data_received, - const CompletionCallback& completion, - const ErrorCallback& error) { - return make_scoped_ptr<Mic>( - new MicWin(sample_rate, data_received, completion, error)); -} - -} // namespace speech -} // namespace cobalt
diff --git a/src/cobalt/speech/microphone.h b/src/cobalt/speech/microphone.h new file mode 100644 index 0000000..4c0d87b --- /dev/null +++ b/src/cobalt/speech/microphone.h
@@ -0,0 +1,72 @@ +/* + * Copyright 2016 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_SPEECH_MICROPHONE_H_ +#define COBALT_SPEECH_MICROPHONE_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/optional.h" + +namespace cobalt { +namespace speech { + +// An abstract class is used for interacting platform specific microphone. +class Microphone { + public: + struct Options { + Options() + : enable_fake_microphone(false), + external_audio_data(NULL), + audio_data_size(0) {} + + // Use fake microphone if this flag is set to true. + bool enable_fake_microphone; + // The following members are only applicable to the fake microphone. + // + // External audio input data for microphone input. + const uint8* external_audio_data; + // Audio data size. + int audio_data_size; + // Input file path. + base::optional<FilePath> file_path; + }; + + virtual ~Microphone() {} + + // Opens microphone port and starts recording audio. + virtual bool Open() = 0; + // Reads audio data from microphone. The return value is the bytes that were + // read from microphone. Negative value indicates a read error. |data_size| is + // the requested read bytes. + virtual int Read(char* out_data, int data_size) = 0; + // Closes microphone port and stops recording audio. + virtual bool Close() = 0; + // Returns the minimum requested bytes per microphone read. + virtual int MinMicrophoneReadInBytes() = 0; + // Returns true if the microphone is valid. + virtual bool IsValid() = 0; + + protected: + Microphone() {} +}; + +} // namespace speech +} // namespace cobalt + +#endif // COBALT_SPEECH_MICROPHONE_H_
diff --git a/src/cobalt/speech/microphone_fake.cc b/src/cobalt/speech/microphone_fake.cc new file mode 100644 index 0000000..0dad830 --- /dev/null +++ b/src/cobalt/speech/microphone_fake.cc
@@ -0,0 +1,189 @@ +/* + * Copyright 2016 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/speech/microphone_fake.h" + +#if defined(ENABLE_FAKE_MICROPHONE) + +#include <algorithm> + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/rand_util.h" +#include "cobalt/audio/audio_file_reader.h" +#include "starboard/file.h" +#include "starboard/memory.h" +#include "starboard/time.h" + +namespace cobalt { +namespace speech { + +namespace { +const int kMaxBufferSize = 1024 * 1024; +const int kMinMicrophoneReadInBytes = 1024; +// The possiblity of microphone creation failed is 1/20. +const int kCreationRange = 20; +// The possiblity of microphone open failed is 1/20. +const int kOpenRange = 20; +// The possiblity of microphone read failed is 1/300. +const int kReadRange = 300; +// The possiblity of microphone close failed is 1/20. +const int kCloseRange = 20; +const int kFailureNumber = 5; + +bool ShouldFail(int range) { + return base::RandGenerator(range) == kFailureNumber; +} + +} // namespace + +MicrophoneFake::MicrophoneFake(const Options& options) + : Microphone(), + read_data_from_file_(options.audio_data_size == 0), + file_length_(-1), + read_index_(0), + is_valid_(!ShouldFail(kCreationRange)) { + if (!is_valid_) { + SB_DLOG(WARNING) << "Mocking microphone creation failed."; + return; + } + + if (read_data_from_file_) { + if (options.file_path) { + SB_DCHECK(!options.file_path->empty()); + // External input file. + file_paths_.push_back(options.file_path.value()); + } else { + FilePath audio_files_path; + SB_CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &audio_files_path)); + audio_files_path = audio_files_path.Append(FILE_PATH_LITERAL("cobalt")) + .Append(FILE_PATH_LITERAL("speech")) + .Append(FILE_PATH_LITERAL("testdata")); + + file_util::FileEnumerator file_enumerator( + audio_files_path, false /* Not recursive */, + file_util::FileEnumerator::FILES); + for (FilePath next = file_enumerator.Next(); !next.empty(); + next = file_enumerator.Next()) { + file_paths_.push_back(next); + } + } + } else { + file_length_ = std::min(options.audio_data_size, kMaxBufferSize); + SB_DCHECK(file_length_ > 0); + file_buffer_.reset(new uint8[file_length_]); + SbMemoryCopy(file_buffer_.get(), options.external_audio_data, file_length_); + } +} + +bool MicrophoneFake::Open() { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + if (ShouldFail(kOpenRange)) { + SB_DLOG(WARNING) << "Mocking microphone open failed."; + return false; + } + + if (read_data_from_file_) { + // Read from local files. + SB_DCHECK(file_paths_.size() != 0); + uint64 random_index = base::RandGenerator(file_paths_.size()); + starboard::ScopedFile file(file_paths_[random_index].value().c_str(), + kSbFileOpenOnly | kSbFileRead, NULL, NULL); + SB_DCHECK(file.IsValid()); + int file_buffer_size = + std::min(static_cast<int>(file.GetSize()), kMaxBufferSize); + SB_DCHECK(file_buffer_size > 0); + + scoped_array<char> audio_input(new char[file_buffer_size]); + int read_bytes = file.ReadAll(audio_input.get(), file_buffer_size); + if (read_bytes < 0) { + return false; + } + + scoped_ptr<audio::AudioFileReader> reader(audio::AudioFileReader::TryCreate( + reinterpret_cast<const uint8*>(audio_input.get()), file_buffer_size, + audio::kSampleTypeInt16)); + const float kSupportedSampleRate = 16000.0f; + const int kSupportedMonoChannel = 1; + if (!reader) { + // If it is not a WAV file, read audio data as raw audio. + file_buffer_.reset(new uint8[file_buffer_size]); + SbMemoryCopy(file_buffer_.get(), audio_input.get(), file_buffer_size); + file_length_ = file_buffer_size; + } else if (reader->sample_type() != ::media::ShellAudioBus::kInt16 || + reader->sample_rate() != kSupportedSampleRate || + reader->number_of_channels() != kSupportedMonoChannel) { + // If it is a WAV file but it doesn't meet the audio input criteria, treat + // it as an error. + return false; + } else { + // Read WAV PCM16 audio data. + int size = + static_cast<int>(reader->number_of_frames() * + audio::GetSampleTypeSize(reader->sample_type())); + file_buffer_.reset(new uint8[size]); + SbMemoryCopy(file_buffer_.get(), reader->sample_data().get(), size); + file_length_ = size; + } + } + return true; +} + +int MicrophoneFake::Read(char* out_data, int data_size) { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + if (ShouldFail(kReadRange)) { + SB_DLOG(WARNING) << "Mocking microphone read failed."; + return -1; + } + + int copy_bytes = std::min(file_length_ - read_index_, data_size); + SbMemoryCopy(out_data, file_buffer_.get() + read_index_, copy_bytes); + read_index_ += copy_bytes; + if (read_index_ == file_length_) { + read_index_ = 0; + } + + return copy_bytes; +} + +bool MicrophoneFake::Close() { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + if (read_data_from_file_) { + file_buffer_.reset(); + file_length_ = -1; + } + + read_index_ = 0; + + if (ShouldFail(kCloseRange)) { + SB_DLOG(WARNING) << "Mocking microphone close failed."; + return false; + } + + return true; +} + +int MicrophoneFake::MinMicrophoneReadInBytes() { + return kMinMicrophoneReadInBytes; +} + +} // namespace speech +} // namespace cobalt + +#endif // defined(ENABLE_FAKE_MICROPHONE)
diff --git a/src/cobalt/speech/microphone_fake.h b/src/cobalt/speech/microphone_fake.h new file mode 100644 index 0000000..068eca3 --- /dev/null +++ b/src/cobalt/speech/microphone_fake.h
@@ -0,0 +1,64 @@ +/* + * Copyright 2016 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_SPEECH_MICROPHONE_FAKE_H_ +#define COBALT_SPEECH_MICROPHONE_FAKE_H_ + +#include "cobalt/speech/speech_configuration.h" + +#if defined(ENABLE_FAKE_MICROPHONE) + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/thread_checker.h" +#include "cobalt/speech/microphone.h" + +namespace cobalt { +namespace speech { + +// Fake microphone to mock the speech input by reading from the pre-recorded +// audio. +class MicrophoneFake : public Microphone { + public: + explicit MicrophoneFake(const Options& options); + ~MicrophoneFake() SB_OVERRIDE {} + + bool Open() SB_OVERRIDE; + int Read(char* out_data, int data_size) SB_OVERRIDE; + bool Close() SB_OVERRIDE; + int MinMicrophoneReadInBytes() SB_OVERRIDE; + bool IsValid() SB_OVERRIDE { return is_valid_; } + + private: + base::ThreadChecker thread_checker_; + + bool read_data_from_file_; + std::vector<FilePath> file_paths_; + scoped_array<uint8> file_buffer_; + int file_length_; + int read_index_; + bool is_valid_; +}; + +} // namespace speech +} // namespace cobalt + +#endif // defined(ENABLE_FAKE_MICROPHONE) +#endif // COBALT_SPEECH_MICROPHONE_FAKE_H_
diff --git a/src/cobalt/speech/microphone_manager.cc b/src/cobalt/speech/microphone_manager.cc new file mode 100644 index 0000000..4414a77 --- /dev/null +++ b/src/cobalt/speech/microphone_manager.cc
@@ -0,0 +1,165 @@ +/* + * Copyright 2016 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/speech/microphone_manager.h" + +#include "cobalt/speech/speech_recognition_error.h" + +namespace cobalt { +namespace speech { + +namespace { +// Size of an audio buffer. +const int kBufferSizeInBytes = 8 * 1024; +// The frequency which we read the data from devices. +const float kMicReadRateInHertz = 60.0f; +} // namespace + +MicrophoneManager::MicrophoneManager( + const DataReceivedCallback& data_received, + const CompletionCallback& completion, const ErrorCallback& error, + const MicrophoneCreator& microphone_creator) + : data_received_callback_(data_received), + completion_callback_(completion), + error_callback_(error), + microphone_creator_(microphone_creator), + state_(kStopped), + thread_("microphone_thread") { + thread_.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0)); +} + +MicrophoneManager::~MicrophoneManager() { + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&MicrophoneManager::DestroyInternal, base::Unretained(this))); +} + +void MicrophoneManager::Open() { + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&MicrophoneManager::OpenInternal, base::Unretained(this))); +} + +void MicrophoneManager::Close() { + thread_.message_loop()->PostTask( + FROM_HERE, + base::Bind(&MicrophoneManager::CloseInternal, base::Unretained(this))); +} + +bool MicrophoneManager::CreateIfNecessary() { + DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread()); + + if (microphone_) { + return true; + } + + microphone_ = microphone_creator_.Run(kBufferSizeInBytes); + if (microphone_ && microphone_->IsValid()) { + state_ = kStopped; + return true; + } else { + DLOG(WARNING) << "Microphone creation failed."; + microphone_.reset(); + state_ = kError; + error_callback_.Run(new SpeechRecognitionError( + SpeechRecognitionError::kAudioCapture, "No microphone available.")); + return false; + } +} + +void MicrophoneManager::OpenInternal() { + DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread()); + + // Try to create a valid microphone if necessary. + if (state_ == kStarted || !CreateIfNecessary()) { + return; + } + + DCHECK(microphone_); + if (!microphone_->Open()) { + state_ = kError; + error_callback_.Run(new SpeechRecognitionError( + SpeechRecognitionError::kAborted, "Microphone open failed.")); + return; + } + + poll_mic_events_timer_.emplace(); + // Setup a timer to poll for input events. + poll_mic_events_timer_->Start( + FROM_HERE, base::TimeDelta::FromMicroseconds(static_cast<int64>( + base::Time::kMicrosecondsPerSecond / kMicReadRateInHertz)), + this, &MicrophoneManager::Read); + state_ = kStarted; +} + +void MicrophoneManager::CloseInternal() { + DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread()); + + if (state_ == kStopped) { + return; + } + + if (poll_mic_events_timer_) { + poll_mic_events_timer_->Stop(); + } + + if (microphone_) { + if (!microphone_->Close()) { + state_ = kError; + error_callback_.Run(new SpeechRecognitionError( + SpeechRecognitionError::kAborted, "Microphone close failed.")); + return; + } + completion_callback_.Run(); + state_ = kStopped; + } +} + +void MicrophoneManager::Read() { + DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread()); + + DCHECK(state_ == kStarted); + DCHECK(microphone_); + DCHECK(microphone_->MinMicrophoneReadInBytes() <= kBufferSizeInBytes); + + int16_t samples[kBufferSizeInBytes / sizeof(int16_t)]; + int read_bytes = + microphone_->Read(reinterpret_cast<char*>(samples), kBufferSizeInBytes); + // If |read_bytes| is zero, nothing should happen. + if (read_bytes > 0 && read_bytes % sizeof(int16_t) == 0) { + size_t frames = read_bytes / sizeof(int16_t); + scoped_ptr<ShellAudioBus> output_audio_bus(new ShellAudioBus( + 1, frames, ShellAudioBus::kInt16, ShellAudioBus::kInterleaved)); + output_audio_bus->Assign(ShellAudioBus(1, frames, samples)); + data_received_callback_.Run(output_audio_bus.Pass()); + } else if (read_bytes != 0) { + state_ = kError; + error_callback_.Run(new SpeechRecognitionError( + SpeechRecognitionError::kAborted, "Microphone read failed.")); + poll_mic_events_timer_->Stop(); + } +} + +void MicrophoneManager::DestroyInternal() { + DCHECK(thread_.message_loop_proxy()->BelongsToCurrentThread()); + + microphone_.reset(); + state_ = kStopped; + poll_mic_events_timer_ = base::nullopt; +} + +} // namespace speech +} // namespace cobalt
diff --git a/src/cobalt/speech/microphone_manager.h b/src/cobalt/speech/microphone_manager.h new file mode 100644 index 0000000..08eaf6e --- /dev/null +++ b/src/cobalt/speech/microphone_manager.h
@@ -0,0 +1,88 @@ +/* + * Copyright 2016 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_SPEECH_MICROPHONE_MANAGER_H_ +#define COBALT_SPEECH_MICROPHONE_MANAGER_H_ + +#include "cobalt/speech/speech_configuration.h" + +#include "base/callback.h" +#include "base/optional.h" +#include "base/threading/thread.h" +#include "base/timer.h" +#include "cobalt/dom/event.h" +#include "cobalt/speech/microphone.h" +#include "media/base/shell_audio_bus.h" + +namespace cobalt { +namespace speech { + +// This class is used for microphone creation, control and destruction. It has +// a self-managed poller to fetch audio data from microphone. +class MicrophoneManager { + public: + typedef ::media::ShellAudioBus ShellAudioBus; + typedef base::Callback<void(scoped_ptr<ShellAudioBus>)> DataReceivedCallback; + typedef base::Callback<void(void)> CompletionCallback; + typedef base::Callback<void(const scoped_refptr<dom::Event>&)> ErrorCallback; + typedef base::Callback<scoped_ptr<Microphone>(int)> MicrophoneCreator; + + MicrophoneManager(const DataReceivedCallback& data_received, + const CompletionCallback& completion, + const ErrorCallback& error, + const MicrophoneCreator& microphone_creator); + + ~MicrophoneManager(); + + // Open microphone to receive voice and start a timer to fetch audio data from + // microphone. + void Open(); + // Close microphone and stop the timer from receiving audio data. + void Close(); + + private: + enum State { kStarted, kStopped, kError }; + + // Returns true if the creation succeeded or the microphone is already a valid + // one. + bool CreateIfNecessary(); + void OpenInternal(); + void CloseInternal(); + void DestroyInternal(); + + // Timer callback for fetching audio data. + void Read(); + + const DataReceivedCallback data_received_callback_; + const CompletionCallback completion_callback_; + const ErrorCallback error_callback_; + const MicrophoneCreator microphone_creator_; + + scoped_ptr<Microphone> microphone_; + + // Microphone state. + State state_; + // Repeat timer to poll mic events. + base::optional<base::RepeatingTimer<MicrophoneManager> > + poll_mic_events_timer_; + // Microphone thread. + base::Thread thread_; +}; + +} // namespace speech +} // namespace cobalt + +#endif // COBALT_SPEECH_MICROPHONE_MANAGER_H_
diff --git a/src/cobalt/speech/microphone_starboard.cc b/src/cobalt/speech/microphone_starboard.cc new file mode 100644 index 0000000..676c0aa --- /dev/null +++ b/src/cobalt/speech/microphone_starboard.cc
@@ -0,0 +1,79 @@ +/* + * Copyright 2016 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/speech/microphone_starboard.h" + +#if defined(SB_USE_SB_MICROPHONE) + +#include "starboard/log.h" + +namespace cobalt { +namespace speech { + +namespace { +// The maximum of microphones which can be supported. Currently only supports +// one microphone. +const int kNumberOfMicrophones = 1; +} // namespace + +MicrophoneStarboard::MicrophoneStarboard(int sample_rate, int buffer_size_bytes) + : Microphone(), + min_microphone_read_in_bytes_(-1), + microphone_(kSbMicrophoneInvalid) { + SbMicrophoneInfo info[kNumberOfMicrophones]; + int microphone_num = SbMicrophoneGetAvailable(info, kNumberOfMicrophones); + + // Loop all the available microphones and create a valid one. + for (int index = 0; index < microphone_num; ++index) { + if (!SbMicrophoneIsSampleRateSupported(info[index].id, sample_rate)) { + continue; + } + + microphone_ = + SbMicrophoneCreate(info[index].id, sample_rate, buffer_size_bytes); + if (!SbMicrophoneIsValid(microphone_)) { + continue; + } + + // Created a microphone successfully. + min_microphone_read_in_bytes_ = info[index].min_read_size; + return; + } +} + +MicrophoneStarboard::~MicrophoneStarboard() { + SbMicrophoneDestroy(microphone_); +} + +bool MicrophoneStarboard::Open() { + SB_DCHECK(SbMicrophoneIsValid(microphone_)); + return SbMicrophoneOpen(microphone_); +} + +int MicrophoneStarboard::Read(char* out_data, int data_size) { + SB_DCHECK(SbMicrophoneIsValid(microphone_)); + return SbMicrophoneRead(microphone_, out_data, data_size); +} + +bool MicrophoneStarboard::Close() { + SB_DCHECK(SbMicrophoneIsValid(microphone_)); + return SbMicrophoneClose(microphone_); +} + +} // namespace speech +} // namespace cobalt + +#endif // defined(SB_USE_SB_MICROPHONE)
diff --git a/src/cobalt/speech/microphone_starboard.h b/src/cobalt/speech/microphone_starboard.h new file mode 100644 index 0000000..9df841a --- /dev/null +++ b/src/cobalt/speech/microphone_starboard.h
@@ -0,0 +1,53 @@ +/* + * Copyright 2016 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_SPEECH_MICROPHONE_STARBOARD_H_ +#define COBALT_SPEECH_MICROPHONE_STARBOARD_H_ + +#include "cobalt/speech/speech_configuration.h" + +#if defined(SB_USE_SB_MICROPHONE) + +#include "cobalt/speech/microphone.h" +#include "starboard/microphone.h" + +namespace cobalt { +namespace speech { + +class MicrophoneStarboard : public Microphone { + public: + MicrophoneStarboard(int sample_rate, int buffer_size_bytes); + ~MicrophoneStarboard() OVERRIDE; + + bool Open() OVERRIDE; + int Read(char* out_data, int data_size) OVERRIDE; + bool Close() OVERRIDE; + int MinMicrophoneReadInBytes() OVERRIDE { + return min_microphone_read_in_bytes_; + } + bool IsValid() OVERRIDE { return SbMicrophoneIsValid(microphone_); } + + private: + // Minimum requested bytes per microphone read. + int min_microphone_read_in_bytes_; + SbMicrophone microphone_; +}; + +} // namespace speech +} // namespace cobalt + +#endif // defined(SB_USE_SB_MICROPHONE) +#endif // COBALT_SPEECH_MICROPHONE_STARBOARD_H_
diff --git a/src/cobalt/speech/sandbox/audio_loader.cc b/src/cobalt/speech/sandbox/audio_loader.cc new file mode 100644 index 0000000..1f83132 --- /dev/null +++ b/src/cobalt/speech/sandbox/audio_loader.cc
@@ -0,0 +1,105 @@ +/* + * Copyright 2016 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/speech/sandbox/audio_loader.h" + +#include <vector> + +namespace cobalt { +namespace speech { +namespace sandbox { + +namespace { +class DummyDecoder : public loader::Decoder { + public: + typedef base::Callback<void(const uint8*, int)> DoneCallback; + + explicit DummyDecoder(const DoneCallback& done_callback) + : done_callback_(done_callback) {} + ~DummyDecoder() OVERRIDE {} + + // This function is used for binding callback for creating DummyDecoder. + static scoped_ptr<Decoder> Create(const DoneCallback& done_callback) { + return scoped_ptr<Decoder>(new DummyDecoder(done_callback)); + } + + // From Decoder. + void DecodeChunk(const char* data, size_t size) OVERRIDE { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Because we load data into memory, set a maximum buffer size. + const int kMaxBufferSize = 1024 * 1024; + if (buffer_.size() + size > kMaxBufferSize) { + return; + } + buffer_.insert(buffer_.end(), data, data + size); + } + + void Finish() OVERRIDE { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (buffer_.size() == 0) { + // No data loaded. + done_callback_.Run(NULL, 0); + return; + } + + done_callback_.Run(reinterpret_cast<uint8*>(&buffer_[0]), buffer_.size()); + } + bool Suspend() OVERRIDE { + NOTIMPLEMENTED(); + return false; + } + void Resume(render_tree::ResourceProvider* /*resource_provider*/) OVERRIDE { + NOTIMPLEMENTED(); + } + + private: + base::ThreadChecker thread_checker_; + std::vector<char> buffer_; + DoneCallback done_callback_; +}; +} // namespace + +AudioLoader::AudioLoader(const GURL& url, + network::NetworkModule* network_module, + const DoneCallback& callback) + : network_module_(network_module), done_callback_(callback) { + DCHECK(url.is_valid()); + DCHECK(!callback.is_null()); + + fetcher_factory_.reset(new loader::FetcherFactory(network_module_)); + loader_ = make_scoped_ptr(new loader::Loader( + base::Bind(&loader::FetcherFactory::CreateFetcher, + base::Unretained(fetcher_factory_.get()), url), + scoped_ptr<loader::Decoder>(new DummyDecoder( + base::Bind(&AudioLoader::OnLoadingDone, base::Unretained(this)))), + base::Bind(&AudioLoader::OnLoadingError, base::Unretained(this)))); +} + +AudioLoader::~AudioLoader() {} + +void AudioLoader::OnLoadingDone(const uint8* data, int size) { + done_callback_.Run(data, size); +} + +void AudioLoader::OnLoadingError(const std::string& error) { + DLOG(WARNING) << "OnLoadingError with error message: " << error; +} + +} // namespace sandbox +} // namespace speech +} // namespace cobalt
diff --git a/src/cobalt/speech/sandbox/audio_loader.h b/src/cobalt/speech/sandbox/audio_loader.h new file mode 100644 index 0000000..921713e --- /dev/null +++ b/src/cobalt/speech/sandbox/audio_loader.h
@@ -0,0 +1,52 @@ +/* + * Copyright 2016 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_SPEECH_SANDBOX_AUDIO_LOADER_H_ +#define COBALT_SPEECH_SANDBOX_AUDIO_LOADER_H_ + +#include <string> + +#include "base/callback.h" +#include "cobalt/loader/fetcher_factory.h" +#include "cobalt/loader/loader.h" +#include "cobalt/network/network_module.h" + +namespace cobalt { +namespace speech { +namespace sandbox { + +class AudioLoader { + public: + typedef base::Callback<void(const uint8*, int)> DoneCallback; + AudioLoader(const GURL& url, network::NetworkModule* network_module, + const DoneCallback& callback); + ~AudioLoader(); + + private: + void OnLoadingDone(const uint8* data, int size); + void OnLoadingError(const std::string& error); + + const DoneCallback done_callback_; + network::NetworkModule* network_module_; + scoped_ptr<loader::FetcherFactory> fetcher_factory_; + scoped_ptr<loader::Loader> loader_; +}; + +} // namespace sandbox +} // namespace speech +} // namespace cobalt + +#endif // COBALT_SPEECH_SANDBOX_AUDIO_LOADER_H_
diff --git a/src/cobalt/speech/sandbox/sandbox.gyp b/src/cobalt/speech/sandbox/sandbox.gyp new file mode 100644 index 0000000..82f36b4 --- /dev/null +++ b/src/cobalt/speech/sandbox/sandbox.gyp
@@ -0,0 +1,54 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a sample sandbox application for experimenting with the Cobalt +# Speech API. + +{ + 'targets': [ + { + 'target_name': 'speech_sandbox', + 'type': '<(final_executable_type)', + 'sources': [ + 'audio_loader.cc', + 'audio_loader.h', + 'speech_sandbox.cc', + 'speech_sandbox.h', + 'speech_sandbox_main.cc', + ], + 'dependencies': [ + '<(DEPTH)/cobalt/audio/audio.gyp:audio', + '<(DEPTH)/cobalt/base/base.gyp:base', + '<(DEPTH)/cobalt/loader/loader.gyp:loader', + '<(DEPTH)/cobalt/network/network.gyp:network', + '<(DEPTH)/cobalt/script/engine.gyp:engine', + '<(DEPTH)/cobalt/speech/speech.gyp:speech', + '<(DEPTH)/cobalt/trace_event/trace_event.gyp:trace_event', + '<(DEPTH)/googleurl/googleurl.gyp:googleurl', + ], + }, + + { + 'target_name': 'speech_sandbox_deploy', + 'type': 'none', + 'dependencies': [ + 'speech_sandbox', + ], + 'variables': { + 'executable_name': 'speech_sandbox', + }, + 'includes': [ '../../../starboard/build/deploy.gypi' ], + }, + ], +}
diff --git a/src/cobalt/speech/sandbox/speech_sandbox.cc b/src/cobalt/speech/sandbox/speech_sandbox.cc new file mode 100644 index 0000000..b0946b6 --- /dev/null +++ b/src/cobalt/speech/sandbox/speech_sandbox.cc
@@ -0,0 +1,100 @@ +/* + * Copyright 2016 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/speech/sandbox/speech_sandbox.h" + +#include "base/path_service.h" +#include "cobalt/script/environment_settings.h" + +namespace cobalt { +namespace speech { +namespace sandbox { + +namespace { +// The maximum number of element depth in the DOM tree. Elements at a level +// deeper than this could be discarded, and will not be rendered. +const int kDOMMaxElementDepth = 32; +} // namespace + +SpeechSandbox::SpeechSandbox(const std::string& file_path_string, + const FilePath& trace_log_path) { + trace_to_file_.reset(new trace_event::ScopedTraceToFile(trace_log_path)); + + network::NetworkModule::Options network_options; + network_options.require_https = false; + network_module_.reset(new network::NetworkModule(network_options)); + + GURL url(file_path_string); + if (url.is_valid()) { + audio_loader_.reset(new AudioLoader( + url, network_module_.get(), + base::Bind(&SpeechSandbox::OnLoadingDone, base::Unretained(this)))); + } else { + FilePath file_path(file_path_string); + if (!file_path.IsAbsolute()) { + FilePath exe_path; + PathService::Get(base::FILE_EXE, &exe_path); + DCHECK(exe_path.IsAbsolute()); + std::string exe_path_string(exe_path.value()); + std::size_t found = exe_path_string.find_last_of("/\\"); + DCHECK_NE(found, std::string::npos); + // Find the executable directory. Using exe_path.DirName() doesn't work + // on Windows based platforms due to the path is mixed with "/" and "\". + exe_path_string = exe_path_string.substr(0, found); + file_path = FilePath(exe_path_string).Append(file_path_string); + DCHECK(file_path.IsAbsolute()); + } + + dom::DOMSettings::Options dom_settings_options; + dom_settings_options.microphone_options.enable_fake_microphone = true; + dom_settings_options.microphone_options.file_path = file_path; + + StartRecognition(dom_settings_options); + } +} + +SpeechSandbox::~SpeechSandbox() { + if (speech_recognition_) { + speech_recognition_->Stop(); + } +} + +void SpeechSandbox::StartRecognition( + const dom::DOMSettings::Options& dom_settings_options) { + scoped_ptr<script::EnvironmentSettings> environment_settings( + new dom::DOMSettings(kDOMMaxElementDepth, NULL, network_module_.get(), + NULL, NULL, NULL, NULL, NULL, NULL, + dom_settings_options)); + DCHECK(environment_settings); + + speech_recognition_ = new SpeechRecognition(environment_settings.get()); + speech_recognition_->set_continuous(true); + speech_recognition_->set_interim_results(true); + speech_recognition_->Start(NULL); +} + +void SpeechSandbox::OnLoadingDone(const uint8* data, int size) { + dom::DOMSettings::Options dom_settings_options; + dom_settings_options.microphone_options.enable_fake_microphone = true; + dom_settings_options.microphone_options.external_audio_data = data; + dom_settings_options.microphone_options.audio_data_size = size; + + StartRecognition(dom_settings_options); +} + +} // namespace sandbox +} // namespace speech +} // namespace cobalt
diff --git a/src/cobalt/speech/sandbox/speech_sandbox.h b/src/cobalt/speech/sandbox/speech_sandbox.h new file mode 100644 index 0000000..15bbd60 --- /dev/null +++ b/src/cobalt/speech/sandbox/speech_sandbox.h
@@ -0,0 +1,60 @@ +/* + * Copyright 2016 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_SPEECH_SANDBOX_SPEECH_SANDBOX_H_ +#define COBALT_SPEECH_SANDBOX_SPEECH_SANDBOX_H_ + +#include <string> + +#include "base/file_path.h" +#include "base/threading/thread.h" +#include "cobalt/dom/dom_settings.h" +#include "cobalt/speech/sandbox/audio_loader.h" +#include "cobalt/speech/speech_recognition.h" +#include "cobalt/trace_event/scoped_trace_to_file.h" + +namespace cobalt { +namespace speech { +namespace sandbox { + +// This class is for speech sandbox application to experiment with voice search. +// It takes a wav audio as audio input for a fake microphone, and starts/stops +// speech recognition. +class SpeechSandbox { + public: + // The constructor takes a file path string for an audio input and a log path + // for tracing. + SpeechSandbox(const std::string& file_path_string, + const FilePath& trace_log_path); + ~SpeechSandbox(); + + private: + void StartRecognition(const dom::DOMSettings::Options& dom_settings_options); + void OnLoadingDone(const uint8* data, int size); + + scoped_ptr<trace_event::ScopedTraceToFile> trace_to_file_; + scoped_ptr<network::NetworkModule> network_module_; + scoped_ptr<AudioLoader> audio_loader_; + scoped_refptr<SpeechRecognition> speech_recognition_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(SpeechSandbox); +}; + +} // namespace sandbox +} // namespace speech +} // namespace cobalt + +#endif // COBALT_SPEECH_SANDBOX_SPEECH_SANDBOX_H_
diff --git a/src/cobalt/speech/sandbox/speech_sandbox_main.cc b/src/cobalt/speech/sandbox/speech_sandbox_main.cc new file mode 100644 index 0000000..2cbb874 --- /dev/null +++ b/src/cobalt/speech/sandbox/speech_sandbox_main.cc
@@ -0,0 +1,68 @@ +/* + * Copyright 2016 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 "base/file_path.h" +#include "base/path_service.h" +#include "base/string_number_conversions.h" +#include "cobalt/base/wrap_main.h" +#include "cobalt/speech/sandbox/speech_sandbox.h" +#include "googleurl/src/gurl.h" + +namespace cobalt { +namespace speech { +namespace sandbox { + +SpeechSandbox* g_speech_sandbox = NULL; + +// The application takes an audio url or path, and a timeout in second. +// The timeout is optional. If it is not set or set to 0, the application +// doesn't shut down. +void StartApplication(int argc, char** argv, const char* /*link*/, + const base::Closure& quit_closure) { + if (argc != 3 && argc != 2) { + LOG(ERROR) << "Usage: " << argv[0] + << " <audio url|path> [timeout in seconds]"; + return; + } + + int timeout = 0; + if (argc == 3) { + base::StringToInt(argv[2], &timeout); + } + + DCHECK(!g_speech_sandbox); + g_speech_sandbox = new SpeechSandbox( + std::string(argv[1]), + FilePath(FILE_PATH_LITERAL("speech_sandbox_trace.json"))); + + if (timeout != 0) { + MessageLoop::current()->PostDelayedTask( + FROM_HERE, quit_closure, base::TimeDelta::FromSeconds(timeout)); + } +} + +void StopApplication() { + DCHECK(g_speech_sandbox); + delete g_speech_sandbox; + g_speech_sandbox = NULL; +} + +} // namespace sandbox +} // namespace speech +} // namespace cobalt + +COBALT_WRAP_BASE_MAIN(cobalt::speech::sandbox::StartApplication, + cobalt::speech::sandbox::StopApplication);
diff --git a/src/cobalt/speech/speech.gyp b/src/cobalt/speech/speech.gyp index 83960c6..c016372 100644 --- a/src/cobalt/speech/speech.gyp +++ b/src/cobalt/speech/speech.gyp
@@ -20,8 +20,23 @@ { 'target_name': 'speech', 'type': 'static_library', + 'msvs_disabled_warnings': [ + # Dereferencing NULL pointer in generated file + # google_streaming_api.pb.cc. + 6011, + ], 'sources': [ - 'mic.h', + 'audio_encoder_flac.cc', + 'audio_encoder_flac.h', + 'endpointer_delegate.cc', + 'endpointer_delegate.h', + 'google_streaming_api.pb.cc', + 'google_streaming_api.pb.h', + 'google_streaming_api.pb.proto', + 'microphone.h', + 'microphone_manager.cc', + 'microphone_manager.h', + 'speech_configuration.h', 'speech_recognition.cc', 'speech_recognition.h', 'speech_recognition_alternative.cc', @@ -43,18 +58,51 @@ 'dependencies': [ '<(DEPTH)/cobalt/base/base.gyp:base', '<(DEPTH)/cobalt/dom/dom.gyp:dom', + '<(DEPTH)/content/browser/speech/speech.gyp:speech', + '<(DEPTH)/third_party/flac/flac.gyp:libflac', + '<(DEPTH)/third_party/protobuf/protobuf.gyp:protobuf_lite', + ], + 'include_dirs': [ + # Get protobuf headers from the chromium tree. + '<(DEPTH)/third_party/protobuf/src', ], 'conditions': [ ['OS=="starboard"', { 'sources': [ - 'mic_starboard.cc', + 'microphone_starboard.cc', + 'microphone_starboard.h', ], }], - ['OS!="starboard" and actual_target_arch in ["linux", "ps3", "win"]', { + ['OS=="starboard" and enable_fake_microphone == 1', { 'sources': [ - 'mic_<(actual_target_arch).cc', + 'microphone_fake.cc', + 'microphone_fake.h', + 'url_fetcher_fake.cc', + 'url_fetcher_fake.h', ], + 'defines': [ + 'ENABLE_FAKE_MICROPHONE', + ], + 'direct_dependent_settings': { + 'defines': [ 'ENABLE_FAKE_MICROPHONE', ], + }, }], + ['cobalt_copy_test_data == 1', { + 'actions': [ + { + 'action_name': 'speech_copy_test_data', + 'variables': { + 'input_files': [ + '<(DEPTH)/cobalt/speech/testdata/', + ], + 'output_dir': 'cobalt/speech/testdata/', + }, + 'includes': [ '../build/copy_test_data.gypi' ], + }, + ], + } + + ], ], }, ],
diff --git a/src/cobalt/speech/speech_configuration.h b/src/cobalt/speech/speech_configuration.h new file mode 100644 index 0000000..97ab76f --- /dev/null +++ b/src/cobalt/speech/speech_configuration.h
@@ -0,0 +1,29 @@ +/* + * Copyright 2016 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_SPEECH_SPEECH_CONFIGURATION_H_ +#define COBALT_SPEECH_SPEECH_CONFIGURATION_H_ + +#include "build/build_config.h" + +#if defined(OS_STARBOARD) +#include "starboard/configuration.h" +#if SB_HAS(MICROPHONE) && SB_VERSION(2) +#define SB_USE_SB_MICROPHONE 1 +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) +#endif // defined(OS_STARBOARD) + +#endif // COBALT_SPEECH_SPEECH_CONFIGURATION_H_
diff --git a/src/cobalt/speech/speech_recognition.cc b/src/cobalt/speech/speech_recognition.cc index 90383ae..06deca0 100644 --- a/src/cobalt/speech/speech_recognition.cc +++ b/src/cobalt/speech/speech_recognition.cc
@@ -28,15 +28,26 @@ SpeechRecognition::SpeechRecognition(script::EnvironmentSettings* settings) : ALLOW_THIS_IN_INITIALIZER_LIST( manager_(base::polymorphic_downcast<dom::DOMSettings*>(settings) - ->fetcher_factory())), + ->network_module(), + base::Bind(&SpeechRecognition::OnEventAvailable, + base::Unretained(this)), + base::polymorphic_downcast<dom::DOMSettings*>(settings) + ->microphone_options())), config_("" /*lang*/, false /*continuous*/, false /*interim_results*/, 1 /*max alternatives*/) {} -void SpeechRecognition::Start() { manager_.Start(config_); } +void SpeechRecognition::Start(script::ExceptionState* exception_state) { + manager_.Start(config_, exception_state); +} void SpeechRecognition::Stop() { manager_.Stop(); } -void SpeechRecognition::Abort() { NOTIMPLEMENTED(); } +void SpeechRecognition::Abort() { manager_.Abort(); } + +bool SpeechRecognition::OnEventAvailable( + const scoped_refptr<dom::Event>& event) { + return DispatchEvent(event); +} } // namespace speech } // namespace cobalt
diff --git a/src/cobalt/speech/speech_recognition.h b/src/cobalt/speech/speech_recognition.h index 4622a9c..020229f 100644 --- a/src/cobalt/speech/speech_recognition.h +++ b/src/cobalt/speech/speech_recognition.h
@@ -36,7 +36,7 @@ // When the start method is called, it represents the moment in time the web // application wishes to begin recognition. - void Start(); + void Start(script::ExceptionState* exception_state); // The stop method represents an instruction to the recognition service to // stop listening to more audio, and to try and return a result using just the // audio that it has already received for this recognition. @@ -107,6 +107,9 @@ private: ~SpeechRecognition() OVERRIDE {} + // Callback from recognition manager. + bool OnEventAvailable(const scoped_refptr<dom::Event>& event); + // Handles main operations of speech recognition including audio encoding, // mic audio retrieving and audio data recognizing. SpeechRecognitionManager manager_;
diff --git a/src/cobalt/speech/speech_recognition_manager.cc b/src/cobalt/speech/speech_recognition_manager.cc index 61b50c7..4eab7de 100644 --- a/src/cobalt/speech/speech_recognition_manager.cc +++ b/src/cobalt/speech/speech_recognition_manager.cc
@@ -17,45 +17,154 @@ #include "cobalt/speech/speech_recognition_manager.h" #include "base/bind.h" +#include "cobalt/base/tokens.h" +#include "cobalt/dom/dom_exception.h" +#if defined(ENABLE_FAKE_MICROPHONE) +#include "cobalt/speech/microphone_fake.h" +#include "cobalt/speech/url_fetcher_fake.h" +#endif // defined(ENABLE_FAKE_MICROPHONE) +#include "cobalt/speech/microphone_manager.h" +#if defined(SB_USE_SB_MICROPHONE) +#include "cobalt/speech/microphone_starboard.h" +#endif // defined(SB_USE_SB_MICROPHONE) +#include "net/url_request/url_fetcher.h" namespace cobalt { namespace speech { namespace { const int kSampleRate = 16000; +const float kAudioPacketDurationInSeconds = 0.1f; + +scoped_ptr<net::URLFetcher> CreateURLFetcher( + const GURL& url, net::URLFetcher::RequestType request_type, + net::URLFetcherDelegate* delegate) { + return make_scoped_ptr<net::URLFetcher>( + net::URLFetcher::Create(url, request_type, delegate)); +} + +scoped_ptr<Microphone> CreateMicrophone(int buffer_size_bytes) { +#if defined(SB_USE_SB_MICROPHONE) + return make_scoped_ptr<Microphone>( + new MicrophoneStarboard(kSampleRate, buffer_size_bytes)); +#else + UNREFERENCED_PARAMETER(buffer_size_bytes); + return scoped_ptr<Microphone>(); +#endif // defined(SB_USE_SB_MICROPHONE) +} + +#if defined(SB_USE_SB_MICROPHONE) +#if defined(ENABLE_FAKE_MICROPHONE) +scoped_ptr<net::URLFetcher> CreateFakeURLFetcher( + const GURL& url, net::URLFetcher::RequestType request_type, + net::URLFetcherDelegate* delegate) { + return make_scoped_ptr<net::URLFetcher>( + new URLFetcherFake(url, request_type, delegate)); +} + +scoped_ptr<Microphone> CreateFakeMicrophone(const Microphone::Options& options, + int /*buffer_size_bytes*/) { + return make_scoped_ptr<Microphone>(new MicrophoneFake(options)); +} +#endif // defined(ENABLE_FAKE_MICROPHONE) +#endif // defined(SB_USE_SB_MICROPHONE) + } // namespace SpeechRecognitionManager::SpeechRecognitionManager( - loader::FetcherFactory* fetcher_factory) + network::NetworkModule* network_module, const EventCallback& event_callback, + const Microphone::Options& microphone_options) : ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), weak_this_(weak_ptr_factory_.GetWeakPtr()), main_message_loop_(base::MessageLoopProxy::current()), - recognizer_(fetcher_factory), - ALLOW_THIS_IN_INITIALIZER_LIST(mic_(Mic::Create( - kSampleRate, base::Bind(&SpeechRecognitionManager::OnDataReceived, - base::Unretained(this)), - base::Bind(&SpeechRecognitionManager::OnDataCompletion, - base::Unretained(this)), - base::Bind(&SpeechRecognitionManager::OnError, - base::Unretained(this))))) {} + event_callback_(event_callback), + endpointer_delegate_(kSampleRate), + state_(kStopped) { + UNREFERENCED_PARAMETER(microphone_options); + + SpeechRecognizer::URLFetcherCreator url_fetcher_creator = + base::Bind(&CreateURLFetcher); + MicrophoneManager::MicrophoneCreator microphone_creator = + base::Bind(&CreateMicrophone); + +#if defined(SB_USE_SB_MICROPHONE) +#if defined(ENABLE_FAKE_MICROPHONE) + if (microphone_options.enable_fake_microphone) { + // If fake microphone is enabled, fake URL fetchers should be enabled as + // well. + url_fetcher_creator = base::Bind(&CreateFakeURLFetcher); + microphone_creator = base::Bind(&CreateFakeMicrophone, microphone_options); + } +#endif // defined(ENABLE_FAKE_MICROPHONE) +#endif // defined(SB_USE_SB_MICROPHONE) + + recognizer_.reset(new SpeechRecognizer( + network_module, base::Bind(&SpeechRecognitionManager::OnRecognizerEvent, + base::Unretained(this)), + url_fetcher_creator)); + microphone_manager_.reset(new MicrophoneManager( + base::Bind(&SpeechRecognitionManager::OnDataReceived, + base::Unretained(this)), + base::Bind(&SpeechRecognitionManager::OnDataCompletion, + base::Unretained(this)), + base::Bind(&SpeechRecognitionManager::OnMicError, base::Unretained(this)), + microphone_creator)); +} SpeechRecognitionManager::~SpeechRecognitionManager() { Stop(); } -void SpeechRecognitionManager::Start(const SpeechRecognitionConfig& config) { +void SpeechRecognitionManager::Start(const SpeechRecognitionConfig& config, + script::ExceptionState* exception_state) { DCHECK(main_message_loop_->BelongsToCurrentThread()); - recognizer_.Start(config); - mic_->Start(); + // If the start method is called on an already started object, the user agent + // MUST throw an InvalidStateError exception and ignore the call. + if (state_ == kStarted) { + dom::DOMException::Raise(dom::DOMException::kInvalidStateErr, + exception_state); + return; + } + + recognizer_->Start(config, kSampleRate); + microphone_manager_->Open(); + endpointer_delegate_.Start(); + state_ = kStarted; } void SpeechRecognitionManager::Stop() { DCHECK(main_message_loop_->BelongsToCurrentThread()); - mic_->Stop(); - recognizer_.Stop(); + // If the stop method is called on an object which is already stopped or being + // stopped, the user agent MUST ignore the call. + if (state_ != kStarted) { + return; + } + + endpointer_delegate_.Stop(); + microphone_manager_->Close(); + recognizer_->Stop(); + state_ = kStopped; + event_callback_.Run(new dom::Event(base::Tokens::soundend())); } -void SpeechRecognitionManager::OnDataReceived(scoped_ptr<AudioBus> audio_bus) { +void SpeechRecognitionManager::Abort() { + DCHECK(main_message_loop_->BelongsToCurrentThread()); + + // If the abort method is called on an object which is already stopped or + // aborting, the user agent MUST ignore the call. + if (state_ != kStarted) { + return; + } + + endpointer_delegate_.Stop(); + microphone_manager_->Close(); + recognizer_->Stop(); + state_ = kAborted; + event_callback_.Run(new dom::Event(base::Tokens::soundend())); +} + +void SpeechRecognitionManager::OnDataReceived( + scoped_ptr<ShellAudioBus> audio_bus) { if (!main_message_loop_->BelongsToCurrentThread()) { // Called from mic thread. main_message_loop_->PostTask( @@ -64,9 +173,13 @@ return; } - // TODO: Encode audio data, and then send it to recognizer. After - // receiving the recognition result from recognizer, fire a speech recognition - // event. + // Stop recognizing if in the abort state. + if (state_ != kAborted) { + if (endpointer_delegate_.IsFirstTimeSoundStarted(*audio_bus)) { + event_callback_.Run(new dom::Event(base::Tokens::soundstart())); + } + recognizer_->RecognizeAudio(audio_bus.Pass(), false); + } } void SpeechRecognitionManager::OnDataCompletion() { @@ -78,19 +191,52 @@ return; } - // TODO: Handle the case that no audio data would be received - // afterwards. + // Stop recognizing if in the abort state. + if (state_ != kAborted) { + // The encoder requires a non-empty final buffer, so encoding a packet of + // silence at the end in case encoder had no data already. + size_t dummy_frames = + static_cast<size_t>(kSampleRate * kAudioPacketDurationInSeconds); + scoped_ptr<ShellAudioBus> dummy_audio_bus(new ShellAudioBus( + 1, dummy_frames, ShellAudioBus::kInt16, ShellAudioBus::kInterleaved)); + dummy_audio_bus->ZeroAllFrames(); + recognizer_->RecognizeAudio(dummy_audio_bus.Pass(), true); + } } -void SpeechRecognitionManager::OnError() { +void SpeechRecognitionManager::OnRecognizerEvent( + const scoped_refptr<dom::Event>& event) { if (!main_message_loop_->BelongsToCurrentThread()) { - // Called from mic thread. + // Called from recognizer thread. main_message_loop_->PostTask( - FROM_HERE, base::Bind(&SpeechRecognitionManager::OnError, weak_this_)); + FROM_HERE, base::Bind(&SpeechRecognitionManager::OnRecognizerEvent, + weak_this_, event)); return; } - // TODO: Handle the case that an error occurred. + // Do not return any information if in the abort state. + if (state_ != kAborted) { + event_callback_.Run(event); + } +} + +void SpeechRecognitionManager::OnMicError( + const scoped_refptr<dom::Event>& event) { + if (!main_message_loop_->BelongsToCurrentThread()) { + // Called from mic thread. + main_message_loop_->PostTask( + FROM_HERE, + base::Bind(&SpeechRecognitionManager::OnMicError, weak_this_, event)); + return; + } + + event_callback_.Run(event); + + // An error is occured in Mic, so stop the energy endpointer and recognizer. + endpointer_delegate_.Stop(); + recognizer_->Stop(); + state_ = kAborted; + event_callback_.Run(new dom::Event(base::Tokens::soundend())); } } // namespace speech
diff --git a/src/cobalt/speech/speech_recognition_manager.h b/src/cobalt/speech/speech_recognition_manager.h index 022d82c..2d622dd 100644 --- a/src/cobalt/speech/speech_recognition_manager.h +++ b/src/cobalt/speech/speech_recognition_manager.h
@@ -19,14 +19,22 @@ #include <string> -#include "cobalt/loader/fetcher_factory.h" -#include "cobalt/speech/mic.h" +#include "cobalt/network/network_module.h" +#include "cobalt/script/exception_state.h" +#include "cobalt/speech/endpointer_delegate.h" +#include "cobalt/speech/microphone.h" +#include "cobalt/speech/speech_configuration.h" #include "cobalt/speech/speech_recognition_config.h" +#include "cobalt/speech/speech_recognition_error.h" +#include "cobalt/speech/speech_recognition_event.h" #include "cobalt/speech/speech_recognizer.h" +#include "media/base/shell_audio_bus.h" namespace cobalt { namespace speech { +class MicrophoneManager; + // Owned by SpeechRecognition to manage major speech recognition logic. // This class interacts with microphone, speech recognition service and audio // encoder. It provides the interface to start/stop microphone and @@ -34,30 +42,52 @@ // class would encode the audio data, then send it to recogniton service. class SpeechRecognitionManager { public: - typedef ::media::AudioBus AudioBus; + typedef ::media::ShellAudioBus ShellAudioBus; + typedef base::Callback<bool(const scoped_refptr<dom::Event>&)> EventCallback; - explicit SpeechRecognitionManager(loader::FetcherFactory* fetcher_factory); + SpeechRecognitionManager(network::NetworkModule* network_module, + const EventCallback& event_callback, + const Microphone::Options& microphone_options); ~SpeechRecognitionManager(); // Start/Stop speech recognizer and microphone. Multiple calls would be // managed by their own class. - void Start(const SpeechRecognitionConfig& config); + void Start(const SpeechRecognitionConfig& config, + script::ExceptionState* exception_state); void Stop(); + void Abort(); private: + enum State { + kStarted, + kStopped, + kAborted, + }; + // Callbacks from mic. - void OnDataReceived(scoped_ptr<AudioBus> audio_bus); + void OnDataReceived(scoped_ptr<ShellAudioBus> audio_bus); void OnDataCompletion(); - void OnError(); + void OnMicError(const scoped_refptr<dom::Event>& event); + + // Callbacks from recognizer. + void OnRecognizerEvent(const scoped_refptr<dom::Event>& event); base::WeakPtrFactory<SpeechRecognitionManager> weak_ptr_factory_; // We construct a WeakPtr upon SpeechRecognitionManager's construction in // order to associate the WeakPtr with the constructing thread. base::WeakPtr<SpeechRecognitionManager> weak_this_; - scoped_refptr<base::MessageLoopProxy> const main_message_loop_; - SpeechRecognizer recognizer_; - scoped_ptr<Mic> mic_; + + // Callback for sending dom events if available. + EventCallback event_callback_; + scoped_ptr<SpeechRecognizer> recognizer_; + + scoped_ptr<MicrophoneManager> microphone_manager_; + + // Delegate of endpointer which is used for detecting sound energy. + EndPointerDelegate endpointer_delegate_; + + State state_; }; } // namespace speech
diff --git a/src/cobalt/speech/speech_recognition_result.h b/src/cobalt/speech/speech_recognition_result.h index 6ebb193..a7432b1 100644 --- a/src/cobalt/speech/speech_recognition_result.h +++ b/src/cobalt/speech/speech_recognition_result.h
@@ -49,7 +49,7 @@ // The final MUST be set to true if this is the final time the speech service // will return this particular index value. If the value is false, then this // represents an interim result that could still be changed. - bool final() const { return final_; } + bool is_final() const { return final_; } DEFINE_WRAPPABLE_TYPE(SpeechRecognitionResult);
diff --git a/src/cobalt/speech/speech_recognizer.cc b/src/cobalt/speech/speech_recognizer.cc index 6355ae7..17806b8 100644 --- a/src/cobalt/speech/speech_recognizer.cc +++ b/src/cobalt/speech/speech_recognizer.cc
@@ -17,26 +17,176 @@ #include "cobalt/speech/speech_recognizer.h" #include "base/bind.h" +#include "base/rand_util.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "cobalt/deprecated/platform_delegate.h" +#include "cobalt/loader/fetcher_factory.h" +#include "cobalt/network/network_module.h" +#include "cobalt/speech/google_streaming_api.pb.h" +#include "cobalt/speech/microphone.h" +#include "cobalt/speech/speech_configuration.h" +#include "cobalt/speech/speech_recognition_error.h" +#include "net/base/escape.h" +#include "net/url_request/url_fetcher.h" + +#if defined(SB_USE_SB_MICROPHONE) +#include "starboard/system.h" +#endif // defined(SB_USE_SB_MICROPHONE) namespace cobalt { namespace speech { -SpeechRecognizer::SpeechRecognizer(loader::FetcherFactory* fetcher_factory) - : fetcher_factory_(fetcher_factory), - thread_("speech_recognizer"), - started_(false) { +namespace { +const char kBaseStreamURL[] = + "https://www.google.com/speech-api/full-duplex/v1"; +const char kUp[] = "up"; +const char kDown[] = "down"; +const char kClient[] = "com.speech.tv"; + +GURL AppendPath(const GURL& url, const std::string& value) { + std::string path(url.path()); + + if (!path.empty()) path += "/"; + + path += net::EscapePath(value); + GURL::Replacements replacements; + replacements.SetPathStr(path); + return url.ReplaceComponents(replacements); +} + +GURL AppendQueryParameter(const GURL& url, const std::string& new_query, + const std::string& value) { + std::string query(url.query()); + + if (!query.empty()) query += "&"; + + query += net::EscapeQueryParamValue(new_query, true); + + if (!value.empty()) { + query += "=" + net::EscapeQueryParamValue(value, true); + } + + GURL::Replacements replacements; + replacements.SetQueryStr(query); + return url.ReplaceComponents(replacements); +} + +SpeechRecognitionResultList::SpeechRecognitionResults +ProcessProtoSuccessResults(const proto::SpeechRecognitionEvent& event) { + DCHECK_EQ(event.status(), proto::SpeechRecognitionEvent::STATUS_SUCCESS); + + SpeechRecognitionResultList::SpeechRecognitionResults results; + for (int i = 0; i < event.result_size(); ++i) { + SpeechRecognitionResult::SpeechRecognitionAlternatives alternatives; + const proto::SpeechRecognitionResult& proto_result = event.result(i); + for (int j = 0; j < proto_result.alternative_size(); ++j) { + const proto::SpeechRecognitionAlternative& proto_alternative = + proto_result.alternative(j); + float confidence = 0.0f; + if (proto_alternative.has_confidence()) { + confidence = proto_alternative.confidence(); + } else if (proto_result.has_stability()) { + confidence = proto_result.stability(); + } + scoped_refptr<SpeechRecognitionAlternative> alternative( + new SpeechRecognitionAlternative(proto_alternative.transcript(), + confidence)); + alternatives.push_back(alternative); + } + + bool final = proto_result.has_final() && proto_result.final(); + scoped_refptr<SpeechRecognitionResult> recognition_result( + new SpeechRecognitionResult(alternatives, final)); + results.push_back(recognition_result); + } + return results; +} + +// TODO: Feed error messages when creating SpeechRecognitionError. +void ProcessAndFireErrorEvent( + const proto::SpeechRecognitionEvent& event, + const SpeechRecognizer::EventCallback& event_callback) { + scoped_refptr<dom::Event> error_event; + switch (event.status()) { + case proto::SpeechRecognitionEvent::STATUS_SUCCESS: + NOTREACHED(); + return; + case proto::SpeechRecognitionEvent::STATUS_NO_SPEECH: + error_event = + new SpeechRecognitionError(SpeechRecognitionError::kNoSpeech, ""); + break; + case proto::SpeechRecognitionEvent::STATUS_ABORTED: + error_event = + new SpeechRecognitionError(SpeechRecognitionError::kAborted, ""); + break; + case proto::SpeechRecognitionEvent::STATUS_AUDIO_CAPTURE: + error_event = + new SpeechRecognitionError(SpeechRecognitionError::kAudioCapture, ""); + break; + case proto::SpeechRecognitionEvent::STATUS_NETWORK: + error_event = + new SpeechRecognitionError(SpeechRecognitionError::kNetwork, ""); + break; + case proto::SpeechRecognitionEvent::STATUS_NOT_ALLOWED: + error_event = + new SpeechRecognitionError(SpeechRecognitionError::kNotAllowed, ""); + break; + case proto::SpeechRecognitionEvent::STATUS_SERVICE_NOT_ALLOWED: + error_event = new SpeechRecognitionError( + SpeechRecognitionError::kServiceNotAllowed, ""); + break; + case proto::SpeechRecognitionEvent::STATUS_BAD_GRAMMAR: + error_event = + new SpeechRecognitionError(SpeechRecognitionError::kBadGrammar, ""); + break; + case proto::SpeechRecognitionEvent::STATUS_LANGUAGE_NOT_SUPPORTED: + error_event = new SpeechRecognitionError( + SpeechRecognitionError::kLanguageNotSupported, ""); + break; + } + + DCHECK(error_event); + event_callback.Run(error_event); +} + +bool IsResponseCodeSuccess(int response_code) { + // NetFetcher only considers success to be if the network request + // was successful *and* we get a 2xx response back. + // TODO: 304s are unexpected since we don't enable the HTTP cache, + // meaning we don't add the If-Modified-Since header to our request. + // However, it's unclear what would happen if we did, so DCHECK. + DCHECK_NE(response_code, 304) << "Unsupported status code"; + return response_code / 100 == 2; +} + +} // namespace + +SpeechRecognizer::SpeechRecognizer(network::NetworkModule* network_module, + const EventCallback& event_callback, + const URLFetcherCreator& fetcher_creator) + : network_module_(network_module), + started_(false), + event_callback_(event_callback), + fetcher_creator_(fetcher_creator), + thread_("speech_recognizer") { thread_.StartWithOptions(base::Thread::Options(MessageLoop::TYPE_IO, 0)); } SpeechRecognizer::~SpeechRecognizer() { Stop(); + // Stopping the thread here to ensure that StopInternal has completed before + // we finish running the destructor. + thread_.Stop(); } -void SpeechRecognizer::Start(const SpeechRecognitionConfig& config) { +void SpeechRecognizer::Start(const SpeechRecognitionConfig& config, + int sample_rate) { // Called by the speech recognition manager thread. - thread_.message_loop()->PostTask(FROM_HERE, - base::Bind(&SpeechRecognizer::StartInternal, - base::Unretained(this), config)); + thread_.message_loop()->PostTask( + FROM_HERE, base::Bind(&SpeechRecognizer::StartInternal, + base::Unretained(this), config, sample_rate)); } void SpeechRecognizer::Stop() { @@ -46,69 +196,195 @@ base::Bind(&SpeechRecognizer::StopInternal, base::Unretained(this))); } -void SpeechRecognizer::RecognizeAudio(scoped_array<uint8> encoded_audio_data, - size_t size, bool is_last_chunk) { +void SpeechRecognizer::RecognizeAudio(scoped_ptr<ShellAudioBus> audio_bus, + bool is_last_chunk) { // Called by the speech recognition manager thread. thread_.message_loop()->PostTask( - FROM_HERE, - base::Bind(&SpeechRecognizer::UploadAudioDataInternal, - base::Unretained(this), base::Passed(&encoded_audio_data), - size, is_last_chunk)); + FROM_HERE, base::Bind(&SpeechRecognizer::UploadAudioDataInternal, + base::Unretained(this), base::Passed(&audio_bus), + is_last_chunk)); } void SpeechRecognizer::OnURLFetchDownloadData( const net::URLFetcher* source, scoped_ptr<std::string> download_data) { DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); - // TODO: process the download data. - NOTIMPLEMENTED(); + const net::URLRequestStatus& status = source->GetStatus(); + const int response_code = source->GetResponseCode(); - UNREFERENCED_PARAMETER(source); - UNREFERENCED_PARAMETER(download_data); + if (source == downstream_fetcher_.get()) { + if (status.is_success() && IsResponseCodeSuccess(response_code)) { + chunked_byte_buffer_.Append(*download_data); + while (chunked_byte_buffer_.HasChunks()) { + scoped_ptr<std::vector<uint8_t> > chunk = + chunked_byte_buffer_.PopChunk().Pass(); + + proto::SpeechRecognitionEvent event; + if (!event.ParseFromString(std::string(chunk->begin(), chunk->end()))) { + DLOG(WARNING) << "Parse proto string error."; + return; + } + + if (event.status() == proto::SpeechRecognitionEvent::STATUS_SUCCESS) { + ProcessAndFireSuccessEvent(ProcessProtoSuccessResults(event)); + } else { + ProcessAndFireErrorEvent(event, event_callback_); + } + } + } else { + event_callback_.Run(new SpeechRecognitionError( + SpeechRecognitionError::kNetwork, "Network response failure.")); + } + } } void SpeechRecognizer::OnURLFetchComplete(const net::URLFetcher* source) { DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); UNREFERENCED_PARAMETER(source); + // no-op. } -void SpeechRecognizer::StartInternal(const SpeechRecognitionConfig& config) { +void SpeechRecognizer::StartInternal(const SpeechRecognitionConfig& config, + int sample_rate) { DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); + if (started_) { // Recognizer is already started. return; } started_ = true; - // TODO: set up url fetchers with this URLFetcherDelegate. - NOTIMPLEMENTED(); + encoder_.reset(new AudioEncoderFlac(sample_rate)); - UNREFERENCED_PARAMETER(config); - UNREFERENCED_PARAMETER(fetcher_factory_); + // Required for streaming on both up and down connections. + std::string pair = base::Uint64ToString(base::RandUint64()); + + // Set up down stream first. + GURL down_url(kBaseStreamURL); + down_url = AppendPath(down_url, kDown); + down_url = AppendQueryParameter(down_url, "pair", pair); + // Use protobuffer as the output format. + down_url = AppendQueryParameter(down_url, "output", "pb"); + + downstream_fetcher_ = + fetcher_creator_.Run(down_url, net::URLFetcher::GET, this); + downstream_fetcher_->SetRequestContext( + network_module_->url_request_context_getter()); + downstream_fetcher_->Start(); + + // Up stream. + GURL up_url(kBaseStreamURL); + up_url = AppendPath(up_url, kUp); + up_url = AppendQueryParameter(up_url, "client", kClient); + up_url = AppendQueryParameter(up_url, "pair", pair); + up_url = AppendQueryParameter(up_url, "output", "pb"); + + const char* speech_api_key = ""; +#if defined(OS_STARBOARD) +#if SB_VERSION(2) + const int kSpeechApiKeyLength = 100; + char buffer[kSpeechApiKeyLength] = {0}; + bool result = SbSystemGetProperty(kSbSystemPropertySpeechApiKey, buffer, + SB_ARRAY_SIZE_INT(buffer)); + SB_DCHECK(result); + speech_api_key = result ? buffer : ""; +#endif // SB_VERSION(2) +#endif // defined(OS_STARBOARD) + + up_url = AppendQueryParameter(up_url, "key", speech_api_key); + + // Language is required. If no language is specified, use the system language. + if (!config.lang.empty()) { + up_url = AppendQueryParameter(up_url, "lang", config.lang); + } else { + up_url = AppendQueryParameter( + up_url, "lang", + cobalt::deprecated::PlatformDelegate::Get()->GetSystemLanguage()); + } + + if (config.max_alternatives) { + up_url = AppendQueryParameter(up_url, "maxAlternatives", + base::UintToString(config.max_alternatives)); + } + + if (config.continuous) { + up_url = AppendQueryParameter(up_url, "continuous", ""); + } + if (config.interim_results) { + up_url = AppendQueryParameter(up_url, "interim", ""); + } + + upstream_fetcher_ = fetcher_creator_.Run(up_url, net::URLFetcher::POST, this); + upstream_fetcher_->SetRequestContext( + network_module_->url_request_context_getter()); + upstream_fetcher_->SetChunkedUpload(encoder_->GetMimeType()); + upstream_fetcher_->Start(); } void SpeechRecognizer::StopInternal() { DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); + if (!started_) { // Recognizer is not started. return; } started_ = false; - // TODO: terminate url fetchers. - NOTIMPLEMENTED(); + upstream_fetcher_.reset(); + downstream_fetcher_.reset(); + encoder_.reset(); + + // Clear the final results. + final_results_.clear(); + // Clear any remaining audio data. + chunked_byte_buffer_.Clear(); } void SpeechRecognizer::UploadAudioDataInternal( - scoped_array<uint8> encoded_audio_data, size_t size, bool is_last_chunk) { + scoped_ptr<ShellAudioBus> audio_bus, bool is_last_chunk) { + DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); + DCHECK(audio_bus); + + std::string encoded_audio_data; + if (encoder_) { + encoder_->Encode(audio_bus.get()); + if (is_last_chunk) { + encoder_->Finish(); + } + encoded_audio_data = encoder_->GetAndClearAvailableEncodedData(); + } + + if (upstream_fetcher_ && !encoded_audio_data.empty()) { + upstream_fetcher_->AppendChunkToUpload(encoded_audio_data, is_last_chunk); + } +} + +void SpeechRecognizer::ProcessAndFireSuccessEvent( + const SpeechRecognitionResults& new_results) { DCHECK_EQ(thread_.message_loop(), MessageLoop::current()); - // TODO: upload encoded audio data chunk. - NOTIMPLEMENTED(); + SpeechRecognitionResults success_results; + size_t total_size = final_results_.size() + new_results.size(); + success_results.reserve(total_size); + success_results = final_results_; + success_results.insert(success_results.end(), new_results.begin(), + new_results.end()); - UNREFERENCED_PARAMETER(encoded_audio_data); - UNREFERENCED_PARAMETER(size); - UNREFERENCED_PARAMETER(is_last_chunk); + size_t result_index = final_results_.size(); + // Update final results list. + for (size_t i = 0; i < new_results.size(); ++i) { + if (new_results[i]->is_final()) { + final_results_.push_back(new_results[i]); + } + } + + scoped_refptr<SpeechRecognitionResultList> recognition_list( + new SpeechRecognitionResultList(success_results)); + scoped_refptr<SpeechRecognitionEvent> recognition_event( + new SpeechRecognitionEvent(SpeechRecognitionEvent::kResult, + static_cast<uint32>(result_index), + recognition_list)); + event_callback_.Run(recognition_event); } } // namespace speech
diff --git a/src/cobalt/speech/speech_recognizer.h b/src/cobalt/speech/speech_recognizer.h index c1900c7..5dac2c6 100644 --- a/src/cobalt/speech/speech_recognizer.h +++ b/src/cobalt/speech/speech_recognizer.h
@@ -18,17 +18,23 @@ #define COBALT_SPEECH_SPEECH_RECOGNIZER_H_ #include <string> +#include <vector> #include "base/threading/thread.h" -#include "cobalt/loader/fetcher_factory.h" +#include "cobalt/network/network_module.h" +#include "cobalt/speech/audio_encoder_flac.h" #include "cobalt/speech/speech_recognition_config.h" +#include "cobalt/speech/speech_recognition_event.h" +#include "content/browser/speech/chunked_byte_buffer.h" +#include "media/base/audio_bus.h" +#include "net/url_request/url_fetcher.h" #include "net/url_request/url_fetcher_delegate.h" namespace cobalt { namespace speech { -// Interacts with Google speech recogniton service, and then parses recognition -// results and forms speech recogniton event. +// Interacts with Google speech recognition service, and then parses recognition +// results and forms speech recognition event. // It creates an upstream fetcher to upload the encoded audio and a downstream // fetcher to fetch the speech recognition result. The fetched speech // recognition result is parsed by JSON parser and a SpeechRecognitionEvent, @@ -36,18 +42,27 @@ // manager. class SpeechRecognizer : public net::URLFetcherDelegate { public: - explicit SpeechRecognizer(loader::FetcherFactory* fetcher_factory); + typedef ::media::ShellAudioBus ShellAudioBus; + typedef base::Callback<void(const scoped_refptr<dom::Event>&)> EventCallback; + typedef SpeechRecognitionResultList::SpeechRecognitionResults + SpeechRecognitionResults; + typedef base::Callback<scoped_ptr<net::URLFetcher>( + const GURL&, net::URLFetcher::RequestType, net::URLFetcherDelegate*)> + URLFetcherCreator; + + SpeechRecognizer(network::NetworkModule* network_module, + const EventCallback& event_callback, + const URLFetcherCreator& fetcher_creator); ~SpeechRecognizer() OVERRIDE; // Multiple calls to Start/Stop are allowed, the implementation should take // care of multiple calls. // Start speech recognizer. - void Start(const SpeechRecognitionConfig& config); + void Start(const SpeechRecognitionConfig& config, int sample_rate); // Stop speech recognizer. void Stop(); // An encoded audio data is available and ready to be recognized. - void RecognizeAudio(scoped_array<uint8> encoded_audio_data, size_t size, - bool is_last_chunk); + void RecognizeAudio(scoped_ptr<ShellAudioBus> audio_bus, bool is_last_chunk); // net::URLFetcherDelegate interface void OnURLFetchDownloadData(const net::URLFetcher* source, @@ -58,18 +73,33 @@ int64 /*current*/, int64 /*total*/) OVERRIDE {} private: - void StartInternal(const SpeechRecognitionConfig& config); + void StartInternal(const SpeechRecognitionConfig& config, int sample_rate); void StopInternal(); - - void UploadAudioDataInternal(scoped_array<uint8> encoded_audio_data, - size_t size, bool is_last_chunk); + void UploadAudioDataInternal(scoped_ptr<ShellAudioBus> audio_bus, + bool is_last_chunk); + void ProcessAndFireSuccessEvent(const SpeechRecognitionResults& new_results); // This is used for creating fetchers. - loader::FetcherFactory* fetcher_factory_; - // Speech recognizer is operating in its own thread. - base::Thread thread_; + network::NetworkModule* network_module_; // Track the start/stop state of speech recognizer. bool started_; + + // Encoder for encoding raw audio data to flac codec. + scoped_ptr<AudioEncoderFlac> encoder_; + // Fetcher for posting the audio data. + scoped_ptr<net::URLFetcher> upstream_fetcher_; + // Fetcher for receiving the streaming results. + scoped_ptr<net::URLFetcher> downstream_fetcher_; + // Used to send speech recognition event. + const EventCallback event_callback_; + // Used to create url fetcher. + const URLFetcherCreator fetcher_creator_; + // Used for processing proto buffer data. + content::ChunkedByteBuffer chunked_byte_buffer_; + // Used for accumulating final results. + SpeechRecognitionResults final_results_; + // Speech recognizer is operating in its own thread. + base::Thread thread_; }; } // namespace speech
diff --git a/src/cobalt/speech/testdata/audio1.raw b/src/cobalt/speech/testdata/audio1.raw new file mode 100644 index 0000000..5ebf79d --- /dev/null +++ b/src/cobalt/speech/testdata/audio1.raw Binary files differ
diff --git a/src/cobalt/speech/testdata/audio2.raw b/src/cobalt/speech/testdata/audio2.raw new file mode 100644 index 0000000..35413b7 --- /dev/null +++ b/src/cobalt/speech/testdata/audio2.raw Binary files differ
diff --git a/src/cobalt/speech/testdata/audio3.raw b/src/cobalt/speech/testdata/audio3.raw new file mode 100644 index 0000000..c503c9e --- /dev/null +++ b/src/cobalt/speech/testdata/audio3.raw Binary files differ
diff --git a/src/cobalt/speech/testdata/quit.raw b/src/cobalt/speech/testdata/quit.raw new file mode 100644 index 0000000..a01dfc4 --- /dev/null +++ b/src/cobalt/speech/testdata/quit.raw Binary files differ
diff --git a/src/cobalt/speech/url_fetcher_fake.cc b/src/cobalt/speech/url_fetcher_fake.cc new file mode 100644 index 0000000..2d621cb --- /dev/null +++ b/src/cobalt/speech/url_fetcher_fake.cc
@@ -0,0 +1,176 @@ +/* + * Copyright 2016 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/speech/url_fetcher_fake.h" + +#if defined(ENABLE_FAKE_MICROPHONE) + +#include "base/basictypes.h" +#include "base/sys_byteorder.h" +#include "cobalt/speech/google_streaming_api.pb.h" +#include "net/url_request/url_fetcher_delegate.h" + +namespace cobalt { +namespace speech { +namespace { +const int kDownloadTimerInterval = 100; + +struct RecognitionAlternative { + const char* transcript; + float confidence; +}; + +const RecognitionAlternative kAlternatives_1[] = { + {"tube", 0.0f}, +}; + +const RecognitionAlternative kAlternatives_2[] = { + {"to be", 0.0f}, +}; + +const RecognitionAlternative kAlternatives_3[] = { + {" or not", 0.0f}, +}; + +const RecognitionAlternative kAlternatives_4[] = { + {" or not to be", 0.0f}, {"to be", 0.0f}, +}; + +const RecognitionAlternative kAlternatives_5[] = { + {"to be or not to be", 0.728f}, {"2 B or not to be", 0.0f}, +}; + +const RecognitionAlternative kAlternatives_6[] = { + {"that", 0.0f}, {"is the", 0.0f}, +}; + +const RecognitionAlternative kAlternatives_7[] = { + {"that", 0.0f}, {"is the question", 0.0f}, +}; + +const RecognitionAlternative kAlternatives_8[] = { + {"that is", 0.0f}, {"the question", 0.0f}, +}; + +const RecognitionAlternative kAlternatives_9[] = { + {"that is the question", 0.8577f}, {"that is a question", 0.0f}, +}; + +struct RecognitionResult { + const RecognitionAlternative* alternatives; + bool final; + size_t number_of_alternatives; +}; + +const RecognitionResult kRecognitionResults[] = { + {kAlternatives_1, false, SB_ARRAY_SIZE_INT(kAlternatives_1)}, + {kAlternatives_2, false, SB_ARRAY_SIZE_INT(kAlternatives_2)}, + {kAlternatives_3, false, SB_ARRAY_SIZE_INT(kAlternatives_3)}, + {kAlternatives_4, false, SB_ARRAY_SIZE_INT(kAlternatives_4)}, + {kAlternatives_5, true, SB_ARRAY_SIZE_INT(kAlternatives_5)}, + {kAlternatives_6, false, SB_ARRAY_SIZE_INT(kAlternatives_6)}, + {kAlternatives_7, false, SB_ARRAY_SIZE_INT(kAlternatives_7)}, + {kAlternatives_8, false, SB_ARRAY_SIZE_INT(kAlternatives_8)}, + {kAlternatives_9, true, SB_ARRAY_SIZE_INT(kAlternatives_9)}, +}; + +std::string GetMockProtoResult(int index) { + proto::SpeechRecognitionEvent proto_event; + proto_event.set_status(proto::SpeechRecognitionEvent::STATUS_SUCCESS); + proto::SpeechRecognitionResult* proto_result = proto_event.add_result(); + proto_result->set_final(kRecognitionResults[index].final); + const RecognitionAlternative* recognition_alternatives = + kRecognitionResults[index].alternatives; + + for (size_t i = 0; i < kRecognitionResults[index].number_of_alternatives; + ++i) { + proto::SpeechRecognitionAlternative* proto_alternative = + proto_result->add_alternative(); + proto_alternative->set_confidence(recognition_alternatives[i].confidence); + proto_alternative->set_transcript(recognition_alternatives[i].transcript); + } + + std::string response_string; + proto_event.SerializeToString(&response_string); + + // Prepend 4 byte prefix length indication to the protobuf message as + // envisaged by the google streaming recognition webservice protocol. + uint32_t prefix = + base::HostToNet32(static_cast<uint32_t>(response_string.size())); + response_string.insert(0, reinterpret_cast<char*>(&prefix), sizeof(prefix)); + return response_string; +} + +} // namespace + +URLFetcherFake::URLFetcherFake(const GURL& url, + net::URLFetcher::RequestType /*request_type*/, + net::URLFetcherDelegate* delegate) + : original_url_(url), + delegate_(delegate), + is_chunked_upload_(false), + download_index_(0) {} + +URLFetcherFake::~URLFetcherFake() {} + +void URLFetcherFake::SetChunkedUpload( + const std::string& /*upload_content_type*/) { + is_chunked_upload_ = true; +} + +void URLFetcherFake::AppendChunkToUpload(const std::string& /*data*/, + bool /*is_last_chunk*/) { + SB_DCHECK(is_chunked_upload_); + // no-op. +} + +void URLFetcherFake::SetRequestContext( + net::URLRequestContextGetter* /*request_context_getter*/) { + // no-op. +} + +void URLFetcherFake::Start() { + if (!is_chunked_upload_) { + download_timer_.emplace(); + download_timer_->Start( + FROM_HERE, base::TimeDelta::FromMilliseconds(kDownloadTimerInterval), + this, &URLFetcherFake::OnURLFetchDownloadData); + } +} + +const net::URLRequestStatus& URLFetcherFake::GetStatus() const { + return fake_status_; +} + +int URLFetcherFake::GetResponseCode() const { return 200; } + +void URLFetcherFake::OnURLFetchDownloadData() { + SB_DCHECK(!is_chunked_upload_); + std::string result = GetMockProtoResult(download_index_); + delegate_->OnURLFetchDownloadData( + this, make_scoped_ptr<std::string>(new std::string(result))); + download_index_++; + if (download_index_ == + static_cast<int>(SB_ARRAY_SIZE_INT(kRecognitionResults))) { + download_index_ = 0; + download_timer_->Stop(); + } +} + +} // namespace speech +} // namespace cobalt + +#endif // defined(ENABLE_FAKE_MICROPHONE)
diff --git a/src/cobalt/speech/url_fetcher_fake.h b/src/cobalt/speech/url_fetcher_fake.h new file mode 100644 index 0000000..cf3c150 --- /dev/null +++ b/src/cobalt/speech/url_fetcher_fake.h
@@ -0,0 +1,156 @@ +/* + * Copyright 2016 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_SPEECH_URL_FETCHER_FAKE_H_ +#define COBALT_SPEECH_URL_FETCHER_FAKE_H_ + +#include "cobalt/speech/speech_configuration.h" + +#if defined(ENABLE_FAKE_MICROPHONE) + +#include <string> + +#include "base/optional.h" +#include "base/timer.h" +#include "googleurl/src/gurl.h" +#include "net/base/host_port_pair.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_status.h" + +namespace cobalt { +namespace speech { + +class URLFetcherFake : public net::URLFetcher { + public: + URLFetcherFake(const GURL& url, net::URLFetcher::RequestType request_type, + net::URLFetcherDelegate* delegate); + virtual ~URLFetcherFake(); + + // URLFetcher implementation: + void SetUploadData(const std::string& /*upload_content_type*/, + const std::string& /*upload_content*/) OVERRIDE { + NOTREACHED(); + } + void SetChunkedUpload(const std::string& /*upload_content_type*/) OVERRIDE; + void AppendChunkToUpload(const std::string& data, + bool is_last_chunk) OVERRIDE; + void SetLoadFlags(int /*load_flags*/) OVERRIDE { NOTREACHED(); } + int GetLoadFlags() const OVERRIDE { + NOTREACHED(); + return 0; + } + void SetReferrer(const std::string& /*referrer*/) OVERRIDE { NOTREACHED(); } + void SetExtraRequestHeaders( + const std::string& /*extra_request_headers*/) OVERRIDE { + NOTREACHED(); + } + void AddExtraRequestHeader(const std::string& /*header_line*/) OVERRIDE { + NOTREACHED(); + } + void GetExtraRequestHeaders( + net::HttpRequestHeaders* /*headers*/) const OVERRIDE { + NOTREACHED(); + } + void SetRequestContext( + net::URLRequestContextGetter* request_context_getter) OVERRIDE; + void SetFirstPartyForCookies( + const GURL& /*first_party_for_cookies*/) OVERRIDE { + NOTREACHED(); + } + void SetURLRequestUserData( + const void* /*key*/, + const CreateDataCallback& /*create_data_callback*/) OVERRIDE { + NOTREACHED(); + } + void SetStopOnRedirect(bool /*stop_on_redirect*/) OVERRIDE { NOTREACHED(); } + void SetAutomaticallyRetryOn5xx(bool /*retry*/) OVERRIDE { NOTREACHED(); } + void SetMaxRetriesOn5xx(int /*max_retries*/) OVERRIDE { NOTREACHED(); } + int GetMaxRetriesOn5xx() const OVERRIDE { + NOTREACHED(); + return 0; + } + base::TimeDelta GetBackoffDelay() const OVERRIDE { + NOTREACHED(); + return base::TimeDelta(); + } + void SetAutomaticallyRetryOnNetworkChanges(int /*max_retries*/) OVERRIDE { + NOTREACHED(); + } + void SaveResponseToFileAtPath( + const FilePath& /*file_path*/, + scoped_refptr<base::TaskRunner> /*file_task_runner*/) OVERRIDE { + NOTREACHED(); + } + void SaveResponseToTemporaryFile( + scoped_refptr<base::TaskRunner> /*file_task_runner*/) OVERRIDE { + NOTREACHED(); + } + void DiscardResponse() OVERRIDE { NOTREACHED(); } + net::HttpResponseHeaders* GetResponseHeaders() const OVERRIDE { + NOTREACHED(); + return NULL; + } + net::HostPortPair GetSocketAddress() const OVERRIDE { + NOTREACHED(); + return net::HostPortPair(); + } + bool WasFetchedViaProxy() const OVERRIDE { + NOTREACHED(); + return false; + } + void Start() OVERRIDE; + const GURL& GetOriginalURL() const OVERRIDE { return original_url_; } + const GURL& GetURL() const OVERRIDE { return original_url_; } + const net::URLRequestStatus& GetStatus() const OVERRIDE; + int GetResponseCode() const OVERRIDE; + const net::ResponseCookies& GetCookies() const OVERRIDE { + NOTREACHED(); + return fake_cookies_; + } + bool FileErrorOccurred( + base::PlatformFileError* /*out_error_code*/) const OVERRIDE { + NOTREACHED(); + return false; + } + void ReceivedContentWasMalformed() OVERRIDE { NOTREACHED(); } + bool GetResponseAsString( + std::string* /*out_response_string*/) const OVERRIDE { + NOTREACHED(); + return false; + } + bool GetResponseAsFilePath(bool /*take_ownership*/, + FilePath* /*out_response_path*/) const OVERRIDE { + NOTREACHED(); + return false; + } + + private: + void OnURLFetchDownloadData(); + + GURL original_url_; + net::URLFetcherDelegate* delegate_; + bool is_chunked_upload_; + int download_index_; + net::ResponseCookies fake_cookies_; + net::URLRequestStatus fake_status_; + base::optional<base::RepeatingTimer<URLFetcherFake> > download_timer_; +}; + +} // namespace speech +} // namespace cobalt + +#endif // defined(ENABLE_FAKE_MICROPHONE) +#endif // COBALT_SPEECH_URL_FETCHER_FAKE_H_
diff --git a/src/cobalt/storage/savegame_thread.cc b/src/cobalt/storage/savegame_thread.cc index 423e52f..a36cf48 100644 --- a/src/cobalt/storage/savegame_thread.cc +++ b/src/cobalt/storage/savegame_thread.cc
@@ -26,7 +26,8 @@ SavegameThread::SavegameThread(const Savegame::Options& options) : options_(options), initialized_(true /* manual reset */, false /* initially signalled */), - thread_(new base::Thread("Savegame I/O")) { + thread_(new base::Thread("Savegame I/O")), + num_consecutive_flush_failures_(0) { TRACE_EVENT0("cobalt::storage", __FUNCTION__); thread_->Start(); thread_->message_loop()->PostTask( @@ -88,7 +89,15 @@ DCHECK_EQ(thread_->message_loop(), MessageLoop::current()); if (raw_bytes_ptr->size() > 0) { bool ret = savegame_->Write(*raw_bytes_ptr); - DCHECK(ret); + if (ret) { + num_consecutive_flush_failures_ = 0; + } else { + DLOG(ERROR) << "Save failed."; + const int kMaxConsecutiveFlushFailures = 2; + DCHECK_LT(++num_consecutive_flush_failures_, + kMaxConsecutiveFlushFailures); + return; + } } if (!on_flush_complete.is_null()) {
diff --git a/src/cobalt/storage/savegame_thread.h b/src/cobalt/storage/savegame_thread.h index 1f9b90f..1ffbd77 100644 --- a/src/cobalt/storage/savegame_thread.h +++ b/src/cobalt/storage/savegame_thread.h
@@ -52,7 +52,7 @@ // I/O thread. void ShutdownOnIOThread(); - // runs on the I/O thread to write the database to the savegame's persistent + // Runs on the I/O thread to write the database to the savegame's persistent // storage. void FlushOnIOThread(scoped_ptr<Savegame::ByteVector> raw_bytes_ptr, const base::Closure& on_flush_complete); @@ -74,6 +74,11 @@ // Interface to platform-specific savegame data. scoped_ptr<Savegame> savegame_; + + // How many flush failures have occurred since the last successful flush. + // Flushes (storage writes) may sometimes fail, but we want to make sure + // they're not consistently failing. + int num_consecutive_flush_failures_; }; } // namespace storage
diff --git a/src/cobalt/storage/virtual_file.cc b/src/cobalt/storage/virtual_file.cc index 64904c2..dcb93ed 100644 --- a/src/cobalt/storage/virtual_file.cc +++ b/src/cobalt/storage/virtual_file.cc
@@ -87,7 +87,10 @@ buffer_.resize(offset + bytes); } - memcpy(&buffer_[offset], source, bytes); + if (!buffer_.empty()) { + // std::vector does not define access to underlying array when empty + memcpy(&buffer_[offset], source, bytes); + } return bytes_in; }
diff --git a/src/cobalt/system_window/starboard/system_window.cc b/src/cobalt/system_window/starboard/system_window.cc index 331e028..2f1b24a 100644 --- a/src/cobalt/system_window/starboard/system_window.cc +++ b/src/cobalt/system_window/starboard/system_window.cc
@@ -103,6 +103,15 @@ return math::Size(window_size.width, window_size.height); } +float SystemWindowStarboard::GetVideoPixelRatio() const { + SbWindowSize window_size; + if (!SbWindowGetSize(window_, &window_size)) { + DLOG(WARNING) << "SbWindowGetSize() failed."; + return 1.0; + } + return window_size.video_pixel_ratio; +} + SbWindow SystemWindowStarboard::GetSbWindow() { return window_; } void* SystemWindowStarboard::GetWindowHandle() {
diff --git a/src/cobalt/system_window/starboard/system_window.h b/src/cobalt/system_window/starboard/system_window.h index 7d6a76e..b733bb9 100644 --- a/src/cobalt/system_window/starboard/system_window.h +++ b/src/cobalt/system_window/starboard/system_window.h
@@ -36,6 +36,7 @@ ~SystemWindowStarboard() OVERRIDE; math::Size GetWindowSize() const OVERRIDE; + float GetVideoPixelRatio() const OVERRIDE; // Returns a handle to the Starboard window object. SbWindow GetSbWindow();
diff --git a/src/cobalt/system_window/system_window.h b/src/cobalt/system_window/system_window.h index 4695652..82683e0 100644 --- a/src/cobalt/system_window/system_window.h +++ b/src/cobalt/system_window/system_window.h
@@ -74,6 +74,11 @@ // Returns the dimensions of the window. virtual math::Size GetWindowSize() const = 0; + // video pixel ratio = resolution of video output / resolution of window. Its + // value is usually 1.0. Set it to a value greater than 1.0 allows the video + // to be played in higher resolution than the window. + virtual float GetVideoPixelRatio() const { return 1.f; } + base::EventDispatcher* event_dispatcher() const { return event_dispatcher_; } private:
diff --git a/src/cobalt/trace_event/json_file_outputter.cc b/src/cobalt/trace_event/json_file_outputter.cc index f17f2d3..1f7a30b 100644 --- a/src/cobalt/trace_event/json_file_outputter.cc +++ b/src/cobalt/trace_event/json_file_outputter.cc
@@ -18,12 +18,38 @@ #include <string> +#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) +#include "base/command_line.h" +#endif #include "base/logging.h" #include "base/platform_file.h" +#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) +#include "base/string_piece.h" +#include "cobalt/trace_event/switches.h" +#endif namespace cobalt { namespace trace_event { +// Returns true if and only if log_timed_trace == "on", and +// ENABLE_DEBUG_COMMAND_LINE_SWITCHES is set. +bool ShouldLogTimedTrace() { + bool isTimedTraceSet = false; + +#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) + + CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kLogTimedTrace) && + command_line->GetSwitchValueASCII(switches::kLogTimedTrace) == + "on") { + isTimedTraceSet = true; + } + +#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES + + return isTimedTraceSet; +} + JSONFileOutputter::JSONFileOutputter(const FilePath& output_path) : output_path_(output_path), output_trace_event_call_count_(0), @@ -72,6 +98,14 @@ return; } + if (ShouldLogTimedTrace()) { + // These markers assist in locating the trace data in the log, and can be + // used by scripts to help extract the trace data. + LOG(INFO) << "BEGIN_TRACELOG_MARKER" << base::StringPiece(buffer, length) + << "END_TRACELOG_MARKER"; + return; + } + int count = base::WritePlatformFileAtCurrentPos(file_, buffer, length); if (count < 0) { Close();
diff --git a/src/cobalt/trace_event/switches.cc b/src/cobalt/trace_event/switches.cc new file mode 100644 index 0000000..6cf65e6 --- /dev/null +++ b/src/cobalt/trace_event/switches.cc
@@ -0,0 +1,33 @@ +/* + * Copyright 2016 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/trace_event/switches.h" + +namespace cobalt { +namespace trace_event { +namespace switches { + +#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) +// If this flag is set, then the contents of the timed_trace is sent to the log +// such that it can be collected by examining console output. This may be +// useful on devices where it is difficult to gain access to files written by +// Cobalt. log_timed_trace: on | off. +const char kLogTimedTrace[] = "log_timed_trace"; +#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES + +} // namespace switches +} // namespace trace_event +} // namespace cobalt
diff --git a/src/cobalt/trace_event/switches.h b/src/cobalt/trace_event/switches.h new file mode 100644 index 0000000..98fc0ec --- /dev/null +++ b/src/cobalt/trace_event/switches.h
@@ -0,0 +1,32 @@ +/* + * Copyright 2016 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_TRACE_EVENT_SWITCHES_H_ +#define COBALT_TRACE_EVENT_SWITCHES_H_ + +namespace cobalt { +namespace trace_event { +namespace switches { + +#if defined(ENABLE_DEBUG_COMMAND_LINE_SWITCHES) +extern const char kLogTimedTrace[]; +#endif // ENABLE_DEBUG_COMMAND_LINE_SWITCHES + +} // namespace switches +} // namespace trace_event +} // namespace cobalt + +#endif // COBALT_TRACE_EVENT_SWITCHES_H_
diff --git a/src/cobalt/trace_event/trace_event.gyp b/src/cobalt/trace_event/trace_event.gyp index 75d5c82..4e4279e 100644 --- a/src/cobalt/trace_event/trace_event.gyp +++ b/src/cobalt/trace_event/trace_event.gyp
@@ -27,6 +27,8 @@ 'scoped_event_parser_trace.h', 'scoped_trace_to_file.cc', 'scoped_trace_to_file.h', + 'switches.cc', + 'switches.h', ], 'dependencies': [ '<(DEPTH)/base/base.gyp:base',
diff --git a/src/cobalt/version.h b/src/cobalt/version.h index 28e8c13..3c57db9 100644 --- a/src/cobalt/version.h +++ b/src/cobalt/version.h
@@ -17,6 +17,6 @@ #define COBALT_VERSION_H_ // Cobalt release number. -#define COBALT_VERSION "2" +#define COBALT_VERSION "6" #endif // COBALT_VERSION_H_
diff --git a/src/cobalt/webdriver/ScriptExecutor.idl b/src/cobalt/webdriver/ScriptExecutor.idl index d82d27a..1b77e2f 100644 --- a/src/cobalt/webdriver/ScriptExecutor.idl +++ b/src/cobalt/webdriver/ScriptExecutor.idl
@@ -25,4 +25,4 @@ }; callback ExecuteFunctionCallback = - DOMString(DOMString functionBody, DOMString json_args); + void(ScriptExecutorParams params, ScriptExecutorResult resultHandler);
diff --git a/src/cobalt/webdriver/ScriptExecutorParams.idl b/src/cobalt/webdriver/ScriptExecutorParams.idl new file mode 100644 index 0000000..cda45c5 --- /dev/null +++ b/src/cobalt/webdriver/ScriptExecutorParams.idl
@@ -0,0 +1,25 @@ +/* + * Copyright 2015 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. + */ + +[ + NoInterfaceObject, + Conditional=ENABLE_WEBDRIVER +] +interface ScriptExecutorParams { + readonly attribute object functionObject; + readonly attribute DOMString jsonArgs; + readonly attribute long? asyncTimeout; +};
diff --git a/src/cobalt/webdriver/ScriptExecutorResult.idl b/src/cobalt/webdriver/ScriptExecutorResult.idl new file mode 100644 index 0000000..718059c --- /dev/null +++ b/src/cobalt/webdriver/ScriptExecutorResult.idl
@@ -0,0 +1,24 @@ +/* + * Copyright 2016 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. + */ + +[ + NoInterfaceObject, + Conditional=ENABLE_WEBDRIVER +] +interface ScriptExecutorResult { + void onResult(DOMString result); + void onTimeout(); +};
diff --git a/src/cobalt/webdriver/content/webdriver-init.js b/src/cobalt/webdriver/content/webdriver-init.js index 2a0b120..03c5099 100644 --- a/src/cobalt/webdriver/content/webdriver-init.js +++ b/src/cobalt/webdriver/content/webdriver-init.js
@@ -14,71 +14,84 @@ * limitations under the License. */ +// Run the JSON deserialize algorithm on the value. +// https://www.w3.org/TR/webdriver/#dfn-json-deserialize +webdriverExecutor.deserialize = function(value) { + if (value instanceof Array) { + // Deserialize each of the array's values; + var deserializedArray = []; + var numArgs = value.length; + for (var i = 0; i < numArgs; i++) { + deserializedArray.push(webdriverExecutor.deserialize(value[i])); + } + return deserializedArray; + } else if (value instanceof Object && value.ELEMENT) { + // The argument is a WebElement, as denoted by the presence of the + // ELEMENT property, so get the actual Element the id is mapped to. + return webdriverExecutor.idToElement(value.ELEMENT); + } else if (value instanceof Object) { + // Deserialize each of the object's values. + var deserializedObject = {}; + for (var key in value) { + deserializedObject[key] = webdriverExecutor.deserialize(value[key]); + } + return deserializedObject; + } + return value; +}; + +// Run the JSON clone algorithm on the value. +// https://www.w3.org/TR/webdriver/#dfn-internal-json-clone-algorithm +webdriverExecutor.serialize = function(value, seen) { + if (value === undefined || value === null) { + return null; + } else if (value instanceof Element) { + var webElementId = webdriverExecutor.elementToId(value); + return { "ELEMENT": webElementId }; + } else if (value instanceof Object) { + if (seen.indexOf(value) != -1) { + throw "Reference cycle found trying to serialize."; + } + seen.push(value); + if (value instanceof Array || value instanceof NodeList + || value instanceof HTMLCollection) { + var serializedArray = []; + var length = value.length; + for (var i = 0; i < length; i++) { + serializedArray.push(webdriverExecutor.serialize(value[i], seen)); + } + return serializedArray; + } else { + // Deserialize each of the object's its values. + var serializedObject = {}; + for (var key in value) { + serializedObject[key] = webdriverExecutor.serialize(value[key], seen); + } + return serializedObject; + } + } + return value; +}; + // Set the executeScriptHarness callback function. // The function takes a functionObject and a JSON string representing an array // of arguments. The arguments are applied to the function |functionObject|. -webdriverExecutor.executeScriptHarness = function(functionObject, jsonArgString) { - - // Run the JSON deserialize algorithm on the value. - // https://www.w3.org/TR/webdriver/#dfn-json-deserialize - var deserialize = function(value) { - if (value instanceof Array) { - // Deserialize each of the array's values; - var deserializedArray = []; - var numArgs = value.length; - for (var i = 0; i < numArgs; i++) { - deserializedArray.push(deserialize(value[i])); - } - return deserializedArray; - } else if (value instanceof Object && value.ELEMENT) { - // The argument is a WebElement, as denoted by the presence of the - // ELEMENT property, so get the actual Element the id is mapped to. - return webdriverExecutor.idToElement(value.ELEMENT); - } else if (value instanceof Object) { - // Deserialize each of the object's values. - var deserializedObject = {}; - for (var key in value) { - deserializedObject[key] = deserialize(value[key]); - } - return deserializedObject; +webdriverExecutor.executeScriptHarness = function(params, resultHandler) { + var parameters = webdriverExecutor.deserialize(JSON.parse(params.jsonArgs)); + if (params.asyncTimeout === null) { + var result = params.functionObject.apply(window, parameters); + resultHandler.onResult(JSON.stringify(webdriverExecutor.serialize(result, []))); + } else { + var timeoutCallback = function() { + resultHandler.onTimeout(); } - return value; - }; + var timerId = window.setTimeout(timeoutCallback, params.asyncTimeout); - // Run the JSON clone algorithm on the value. - // https://www.w3.org/TR/webdriver/#dfn-internal-json-clone-algorithm - var serialize = function(value, seen) { - if (value === undefined || value === null) { - return null; - } else if (value instanceof Element) { - var webElementId = webdriverExecutor.elementToId(value); - return { "ELEMENT": webElementId }; - } else if (value instanceof Object) { - if (seen.indexOf(value) != -1) { - throw "Reference cycle found trying to serialize."; - } - seen.push(value); - if (value instanceof Array || value instanceof NodeList - || value instanceof HTMLCollection) { - var serializedArray = []; - var length = value.length; - for (var i = 0; i < length; i++) { - serializedArray.push(serialize(value[i], seen)); - } - return serializedArray; - } else { - // Deserialize each of the object's its values. - var serializedObject = {}; - for (var key in value) { - serializedObject[key] = serialize(value[key], seen); - } - return serializedObject; - } + var resultCallback = function(result) { + resultHandler.onResult(JSON.stringify(webdriverExecutor.serialize(result, []))); + window.clearTimeout(timerId); } - return value; - }; - var parameters = deserialize(JSON.parse(jsonArgString)); - var result = functionObject.apply(window, parameters); - return JSON.stringify(serialize(result, [])); - + parameters.push(resultCallback); + params.functionObject.apply(window, parameters); + } };
diff --git a/src/cobalt/webdriver/element_driver.cc b/src/cobalt/webdriver/element_driver.cc index a5c3315..d295974 100644 --- a/src/cobalt/webdriver/element_driver.cc +++ b/src/cobalt/webdriver/element_driver.cc
@@ -64,10 +64,12 @@ ElementDriver::ElementDriver( const protocol::ElementId& element_id, const base::WeakPtr<dom::Element>& element, ElementMapping* element_mapping, + KeyboardEventInjector keyboard_event_injector, const scoped_refptr<base::MessageLoopProxy>& message_loop) : element_id_(element_id), element_(element), element_mapping_(element_mapping), + keyboard_injector_(keyboard_event_injector), element_message_loop_(message_loop) {} util::CommandResult<std::string> ElementDriver::GetTagName() { @@ -106,7 +108,8 @@ return util::CallOnMessageLoop( element_message_loop_, base::Bind(&ElementDriver::SendKeysInternal, base::Unretained(this), - base::Passed(&events))); + base::Passed(&events)), + protocol::Response::kStaleElementReference); } util::CommandResult<protocol::ElementId> ElementDriver::FindElement( @@ -114,7 +117,8 @@ return util::CallOnMessageLoop( element_message_loop_, base::Bind(&ElementDriver::FindElementsInternal<protocol::ElementId>, - base::Unretained(this), strategy)); + base::Unretained(this), strategy), + protocol::Response::kStaleElementReference); } util::CommandResult<std::vector<protocol::ElementId> > @@ -122,7 +126,8 @@ return util::CallOnMessageLoop( element_message_loop_, base::Bind(&ElementDriver::FindElementsInternal<ElementIdVector>, - base::Unretained(this), strategy)); + base::Unretained(this), strategy), + protocol::Response::kNoSuchElement); } util::CommandResult<bool> ElementDriver::Equals( @@ -130,7 +135,8 @@ return util::CallOnMessageLoop( element_message_loop_, base::Bind(&ElementDriver::EqualsInternal, base::Unretained(this), - other_element_driver)); + other_element_driver), + protocol::Response::kStaleElementReference); } util::CommandResult<base::optional<std::string> > ElementDriver::GetAttribute( @@ -157,7 +163,7 @@ } util::CommandResult<void> ElementDriver::SendKeysInternal( - scoped_ptr<KeyboardEventVector> events) { + scoped_ptr<Keyboard::KeyboardEventVector> events) { typedef util::CommandResult<void> CommandResult; DCHECK_EQ(base::MessageLoopProxy::current(), element_message_loop_); if (!element_) { @@ -174,7 +180,8 @@ if (!element_) { return CommandResult(protocol::Response::kStaleElementReference); } - element_->DispatchEvent((*events)[i]); + + keyboard_injector_.Run(element_.get(), (*events)[i]); } return CommandResult(protocol::Response::kSuccess); }
diff --git a/src/cobalt/webdriver/element_driver.h b/src/cobalt/webdriver/element_driver.h index 22e2ee0..645a0e0 100644 --- a/src/cobalt/webdriver/element_driver.h +++ b/src/cobalt/webdriver/element_driver.h
@@ -28,6 +28,7 @@ #include "cobalt/dom/element.h" #include "cobalt/dom/keyboard_event.h" #include "cobalt/webdriver/element_mapping.h" +#include "cobalt/webdriver/keyboard.h" #include "cobalt/webdriver/protocol/element_id.h" #include "cobalt/webdriver/protocol/keys.h" #include "cobalt/webdriver/protocol/search_strategy.h" @@ -46,9 +47,14 @@ // will map to a method on this class. class ElementDriver { public: + typedef base::Callback<void(scoped_refptr<dom::Element>, + const dom::KeyboardEvent::Data&)> + KeyboardEventInjector; + ElementDriver(const protocol::ElementId& element_id, const base::WeakPtr<dom::Element>& element, ElementMapping* element_mapping, + KeyboardEventInjector keyboard_injector, const scoped_refptr<base::MessageLoopProxy>& message_loop); const protocol::ElementId& element_id() { return element_id_; } @@ -67,7 +73,6 @@ const std::string& property_name); private: - typedef std::vector<scoped_refptr<dom::KeyboardEvent> > KeyboardEventVector; typedef std::vector<protocol::ElementId> ElementIdVector; // Get the dom::Element* that this ElementDriver wraps. This must be called @@ -75,7 +80,7 @@ dom::Element* GetWeakElement(); util::CommandResult<void> SendKeysInternal( - scoped_ptr<KeyboardEventVector> keyboard_events); + scoped_ptr<Keyboard::KeyboardEventVector> keyboard_events); // Shared logic between FindElement and FindElements. template <typename T> @@ -90,6 +95,7 @@ // These should only be accessed from |element_message_loop_|. base::WeakPtr<dom::Element> element_; ElementMapping* element_mapping_; + KeyboardEventInjector keyboard_injector_; scoped_refptr<base::MessageLoopProxy> element_message_loop_; friend class WindowDriver;
diff --git a/src/cobalt/webdriver/execute_test.cc b/src/cobalt/webdriver/execute_test.cc new file mode 100644 index 0000000..6edec33 --- /dev/null +++ b/src/cobalt/webdriver/execute_test.cc
@@ -0,0 +1,304 @@ +/* + * Copyright 2016 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 <algorithm> +#include <vector> + +#include "base/json/json_reader.h" +#include "base/run_loop.h" +#include "cobalt/dom/document.h" +#include "cobalt/script/global_environment.h" +#include "cobalt/script/javascript_engine.h" +#include "cobalt/webdriver/script_executor.h" +#include "cobalt/webdriver/testing/stub_window.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::DefaultValue; +using ::testing::Return; + +namespace cobalt { +namespace webdriver { +namespace { + +class MockElementMapping : public ElementMapping { + public: + MOCK_METHOD1(ElementToId, + protocol::ElementId(const scoped_refptr<dom::Element>&)); + MOCK_METHOD1(IdToElement, + scoped_refptr<dom::Element>(const protocol::ElementId& id)); +}; + +class MockScriptExecutorResult : public ScriptExecutorResult::ResultHandler { + public: + MOCK_METHOD1(OnResult, void(const std::string&)); + MOCK_METHOD0(OnTimeout, void()); +}; + +class JSONScriptExecutorResult : public ScriptExecutorResult::ResultHandler { + public: + void OnResult(const std::string& result) { + json_result_.reset(base::JSONReader::Read(result.c_str())); + } + void OnTimeout() { NOTREACHED(); } + base::Value* json_result() { return json_result_.get(); } + + private: + scoped_ptr<base::Value> json_result_; +}; + +class ScriptExecutorTest : public ::testing::Test { + protected: + void SetUp() OVERRIDE { + stub_window_.reset(new testing::StubWindow()); + script_executor_ = + ScriptExecutor::Create(&element_mapping_, global_environment()); + + ON_CALL(element_mapping_, IdToElement(_)) + .WillByDefault(Return(scoped_refptr<dom::Element>())); + ON_CALL(element_mapping_, ElementToId(_)) + .WillByDefault(Return(protocol::ElementId("bad-id"))); + } + + scoped_refptr<dom::Window> window() { return stub_window_->window(); } + scoped_refptr<script::GlobalEnvironment> global_environment() { + return stub_window_->global_environment(); + } + + protected: + scoped_ptr<testing::StubWindow> stub_window_; + MockElementMapping element_mapping_; + scoped_refptr<ScriptExecutor> script_executor_; +}; + +} // namespace + +TEST_F(ScriptExecutorTest, CreateSyncScript) { + scoped_refptr<ScriptExecutorParams> params = + ScriptExecutorParams::Create(global_environment(), "return 5;", "[]"); + ASSERT_TRUE(params); + EXPECT_NE(reinterpret_cast<intptr_t>(params->function_object()), NULL); + EXPECT_STREQ(params->json_args().c_str(), "[]"); + EXPECT_EQ(params->async_timeout(), base::nullopt); +} + +TEST_F(ScriptExecutorTest, CreateAsyncScript) { + scoped_refptr<ScriptExecutorParams> params = + ScriptExecutorParams::Create(global_environment(), "return 5;", "[]", + base::TimeDelta::FromMilliseconds(5)); + ASSERT_TRUE(params); + EXPECT_NE(reinterpret_cast<intptr_t>(params->function_object()), NULL); + EXPECT_STREQ(params->json_args().c_str(), "[]"); + EXPECT_EQ(params->async_timeout(), 5); +} + +TEST_F(ScriptExecutorTest, CreateInvalidScript) { + scoped_refptr<ScriptExecutorParams> params = + ScriptExecutorParams::Create(global_environment(), "retarn 5ish;", "[]"); + ASSERT_TRUE(params); + EXPECT_EQ(reinterpret_cast<intptr_t>(params->function_object()), NULL); +} + +TEST_F(ScriptExecutorTest, ExecuteSync) { + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), "return \"retval\";", "[]"); + ASSERT_TRUE(params); + MockScriptExecutorResult result_handler; + EXPECT_CALL(result_handler, OnResult(std::string("\"retval\""))); + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); +} + +TEST_F(ScriptExecutorTest, ExecuteAsync) { + // Create a script that will call the async callback after 50 ms, with + // an async timeout of 100 ms. + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), + "var callback = arguments[0];" + "window.setTimeout(function() { callback(72); }, 50);", + "[]", base::TimeDelta::FromMilliseconds(100)); + ASSERT_TRUE(params); + MockScriptExecutorResult result_handler; + EXPECT_CALL(result_handler, OnResult(std::string("72"))); + EXPECT_CALL(result_handler, OnTimeout()).Times(0); + + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); + + // Let the message loop run for 200ms to allow enough time for the async + // script to fire the callback. + base::RunLoop run_loop; + MessageLoop::current()->PostDelayedTask( + FROM_HERE, run_loop.QuitClosure(), + base::TimeDelta::FromMilliseconds(200)); + run_loop.Run(); +} + +TEST_F(ScriptExecutorTest, AsyncTimeout) { + // Create a script that will call the async callback after 10 seconds, with + // an async timeout of 100 ms. + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), + "var callback = arguments[0];" + "window.setTimeout(function() { callback(72); }, 10000);", + "[]", base::TimeDelta::FromMilliseconds(100)); + ASSERT_TRUE(params); + MockScriptExecutorResult result_handler; + EXPECT_CALL(result_handler, OnResult(_)).Times(0); + EXPECT_CALL(result_handler, OnTimeout()).Times(1); + + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); + + // Let the message loop run for 200ms to allow enough time for the async + // timeout to fire. + base::RunLoop run_loop; + MessageLoop::current()->PostDelayedTask( + FROM_HERE, run_loop.QuitClosure(), + base::TimeDelta::FromMilliseconds(200)); + run_loop.Run(); +} + +TEST_F(ScriptExecutorTest, ScriptThrowsException) { + scoped_refptr<ScriptExecutorParams> params = + ScriptExecutorParams::Create(global_environment(), "throw Error()", "[]"); + ASSERT_TRUE(params); + MockScriptExecutorResult result_handler; + EXPECT_FALSE(script_executor_->Execute(params, &result_handler)); +} + +TEST_F(ScriptExecutorTest, ConvertBoolean) { + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), "return arguments[0];", "[true]"); + ASSERT_TRUE(params); + MockScriptExecutorResult result_handler; + EXPECT_CALL(result_handler, OnResult(std::string("true"))); + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); + + params = ScriptExecutorParams::Create(global_environment(), + "return arguments[0];", "[false]"); + ASSERT_TRUE(params); + EXPECT_CALL(result_handler, OnResult(std::string("false"))); + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); +} + +TEST_F(ScriptExecutorTest, ConvertNull) { + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), "return arguments[0];", "[null]"); + ASSERT_TRUE(params); + MockScriptExecutorResult result_handler; + EXPECT_CALL(result_handler, OnResult(std::string("null"))); + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); +} + +TEST_F(ScriptExecutorTest, ConvertNumericType) { + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), "return arguments[0];", "[6]"); + ASSERT_TRUE(params); + MockScriptExecutorResult result_handler; + EXPECT_CALL(result_handler, OnResult(std::string("6"))); + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); + + params = ScriptExecutorParams::Create(global_environment(), + "return arguments[0];", "[-6.4]"); + ASSERT_TRUE(params); + EXPECT_CALL(result_handler, OnResult(std::string("-6.4"))); + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); +} + +TEST_F(ScriptExecutorTest, ConvertString) { + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), "return arguments[0];", "[\"Mr. T\"]"); + ASSERT_TRUE(params); + MockScriptExecutorResult result_handler; + EXPECT_CALL(result_handler, OnResult(std::string("\"Mr. T\""))); + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); +} + +TEST_F(ScriptExecutorTest, ConvertWebElement) { + // Create a dom::Element for the MockElementMapping to return. + scoped_refptr<dom::Element> element = + window()->document()->CreateElement("p"); + EXPECT_CALL(element_mapping_, IdToElement(protocol::ElementId("id123"))) + .WillRepeatedly(Return(element)); + EXPECT_CALL(element_mapping_, ElementToId(element)) + .WillRepeatedly(Return(protocol::ElementId("id123"))); + + // Create a script that will pass a web element argument as a parameter, and + // return it back. This will invoke the lookup to and from an id. + scoped_refptr<ScriptExecutorParams> params = + ScriptExecutorParams::Create(global_environment(), "return arguments[0];", + "[ {\"ELEMENT\": \"id123\"} ]"); + ASSERT_TRUE(params); + + // Execute the script and parse the result as JSON, ensuring we got the same + // web element. + JSONScriptExecutorResult result_handler; + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); + ASSERT_TRUE(result_handler.json_result()); + + std::string element_id; + base::DictionaryValue* dictionary_value; + ASSERT_TRUE(result_handler.json_result()->GetAsDictionary(&dictionary_value)); + EXPECT_TRUE(dictionary_value->GetString(protocol::ElementId::kElementKey, + &element_id)); + EXPECT_STREQ(element_id.c_str(), "id123"); +} + +TEST_F(ScriptExecutorTest, ConvertArray) { + // Create a script that takes an array of numbers as input, and returns an + // array of those numbers incremented by one. + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), + "return [ (arguments[0][0]+1), (arguments[0][1]+1) ];", "[ [5, 6] ]"); + ASSERT_TRUE(params); + + JSONScriptExecutorResult result_handler; + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); + ASSERT_TRUE(result_handler.json_result()); + + base::ListValue* list_value; + ASSERT_TRUE(result_handler.json_result()->GetAsList(&list_value)); + ASSERT_EQ(list_value->GetSize(), 2); + + int value; + EXPECT_TRUE(list_value->GetInteger(0, &value)); + EXPECT_EQ(value, 6); + EXPECT_TRUE(list_value->GetInteger(1, &value)); + EXPECT_EQ(value, 7); +} + +TEST_F(ScriptExecutorTest, ConvertObject) { + // Create a script that takes an Object with two properties as input, and + // returns an Object with one property that is the sum of the other Object's + // properties. + scoped_refptr<ScriptExecutorParams> params = ScriptExecutorParams::Create( + global_environment(), + "return {\"sum\": arguments[0].a + arguments[0].b};", + "[ {\"a\":5, \"b\":6} ]"); + ASSERT_TRUE(params); + + JSONScriptExecutorResult result_handler; + EXPECT_TRUE(script_executor_->Execute(params, &result_handler)); + ASSERT_TRUE(result_handler.json_result()); + + int value; + base::DictionaryValue* dictionary_value; + ASSERT_TRUE(result_handler.json_result()->GetAsDictionary(&dictionary_value)); + EXPECT_TRUE(dictionary_value->GetInteger("sum", &value)); + EXPECT_EQ(value, 11); +} + +} // namespace webdriver +} // namespace cobalt
diff --git a/src/cobalt/webdriver/keyboard.cc b/src/cobalt/webdriver/keyboard.cc index c69496f..32d4fbc 100644 --- a/src/cobalt/webdriver/keyboard.cc +++ b/src/cobalt/webdriver/keyboard.cc
@@ -421,7 +421,7 @@ KeyLocationCode location) { const bool kIsRepeat = false; uint32 modifiers = GetModifierStateBitfield(); - event_vector_->push_back(new dom::KeyboardEvent( + event_vector_->push_back(dom::KeyboardEvent::Data( type, location, modifiers, key_code, char_code, kIsRepeat)); }
diff --git a/src/cobalt/webdriver/keyboard.h b/src/cobalt/webdriver/keyboard.h index feeab6f..57a5c5f 100644 --- a/src/cobalt/webdriver/keyboard.h +++ b/src/cobalt/webdriver/keyboard.h
@@ -32,7 +32,8 @@ kReleaseModifiers, kKeepModifiers, }; - typedef std::vector<scoped_refptr<dom::KeyboardEvent> > KeyboardEventVector; + typedef std::vector<dom::KeyboardEvent::Data> + KeyboardEventVector; static void TranslateToKeyEvents(const std::string& utf8_keys, TerminationBehaviour termination_behaviour, KeyboardEventVector* out_events);
diff --git a/src/cobalt/webdriver/keyboard_test.cc b/src/cobalt/webdriver/keyboard_test.cc index 1f71598..7994bd5 100644 --- a/src/cobalt/webdriver/keyboard_test.cc +++ b/src/cobalt/webdriver/keyboard_test.cc
@@ -31,25 +31,34 @@ namespace webdriver { namespace { -int32 GetKeyCode(const scoped_refptr<dom::KeyboardEvent>& event) { - DCHECK(event); - return event->key_code(); +int32 GetKeyCode(const dom::KeyboardEvent::Data& event) { + scoped_refptr<dom::KeyboardEvent> keyboard_event( + new dom::KeyboardEvent(event)); + return keyboard_event->key_code(); } -int32 GetCharCode(const scoped_refptr<dom::KeyboardEvent>& event) { - return event->char_code(); +int32 GetCharCode(const dom::KeyboardEvent::Data& event) { + scoped_refptr<dom::KeyboardEvent> keyboard_event( + new dom::KeyboardEvent(event)); + return keyboard_event->char_code(); } -uint32 GetModifierBitfield(const scoped_refptr<dom::KeyboardEvent>& event) { - return event->modifiers(); +uint32 GetModifierBitfield(const dom::KeyboardEvent::Data& event) { + scoped_refptr<dom::KeyboardEvent> keyboard_event( + new dom::KeyboardEvent(event)); + return keyboard_event->modifiers(); } -std::string GetType(const scoped_refptr<dom::KeyboardEvent>& event) { - return event->type().c_str(); +std::string GetType(const dom::KeyboardEvent::Data& event) { + scoped_refptr<dom::KeyboardEvent> keyboard_event( + new dom::KeyboardEvent(event)); + return keyboard_event->type().c_str(); } -int GetLocation(const scoped_refptr<dom::KeyboardEvent>& event) { - return event->location(); +int GetLocation(const dom::KeyboardEvent::Data& event) { + scoped_refptr<dom::KeyboardEvent> keyboard_event( + new dom::KeyboardEvent(event)); + return keyboard_event->location(); } class KeyboardTest : public ::testing::Test {
diff --git a/src/cobalt/webdriver/protocol/element_id.cc b/src/cobalt/webdriver/protocol/element_id.cc index 8891cc7..9d03cf9 100644 --- a/src/cobalt/webdriver/protocol/element_id.cc +++ b/src/cobalt/webdriver/protocol/element_id.cc
@@ -19,9 +19,8 @@ namespace cobalt { namespace webdriver { namespace protocol { -namespace { -const char kElementKey[] = "ELEMENT"; -} + +const char ElementId::kElementKey[] = "ELEMENT"; scoped_ptr<base::Value> ElementId::ToValue(const ElementId& element_id) { scoped_ptr<base::DictionaryValue> element_object(new base::DictionaryValue());
diff --git a/src/cobalt/webdriver/protocol/element_id.h b/src/cobalt/webdriver/protocol/element_id.h index c4e0f58..c19dfb5 100644 --- a/src/cobalt/webdriver/protocol/element_id.h +++ b/src/cobalt/webdriver/protocol/element_id.h
@@ -30,6 +30,8 @@ // Opaque type that uniquely identifies an Element from a WebDriver session. class ElementId { public: + static const char kElementKey[]; + // Convert the ElementId to a WebElement JSON object: // https://code.google.com/p/selenium/wiki/JsonWireProtocol#WebElement_JSON_Object static scoped_ptr<base::Value> ToValue(const ElementId& element_id);
diff --git a/src/cobalt/webdriver/protocol/response.h b/src/cobalt/webdriver/protocol/response.h index 28ffe9a..8cfc549 100644 --- a/src/cobalt/webdriver/protocol/response.h +++ b/src/cobalt/webdriver/protocol/response.h
@@ -62,6 +62,9 @@ // An error occurred while executing user supplied JavaScript. kJavaScriptError = 17, + // An operation did not complete before its timeout expired. + kTimeOut = 21, + // The specified window has been closed, or otherwise couldn't be found. kNoSuchWindow = 23,
diff --git a/src/cobalt/webdriver/protocol/server_status.cc b/src/cobalt/webdriver/protocol/server_status.cc index e98ebe2..bcb283c 100644 --- a/src/cobalt/webdriver/protocol/server_status.cc +++ b/src/cobalt/webdriver/protocol/server_status.cc
@@ -16,9 +16,6 @@ #include "cobalt/webdriver/protocol/server_status.h" -// TODO: Support running WebDriver on platforms other than Linux. -#include <sys/utsname.h> - #include "cobalt/version.h" namespace cobalt { @@ -26,11 +23,26 @@ namespace protocol { ServerStatus::ServerStatus() { - struct utsname name_buffer; - if (uname(&name_buffer) == 0) { - os_name_ = name_buffer.sysname; - os_version_ = name_buffer.version; - os_arch_ = name_buffer.machine; + const size_t kSystemPropertyMaxLength = 1024; + char value[kSystemPropertyMaxLength]; + bool result; + + result = SbSystemGetProperty(kSbSystemPropertyPlatformName, value, + kSystemPropertyMaxLength); + if (result) { + os_name_ = value; + } + + result = SbSystemGetProperty(kSbSystemPropertyFirmwareVersion, value, + kSystemPropertyMaxLength); + if (result) { + os_version_ = value; + } + + result = SbSystemGetProperty(kSbSystemPropertyChipsetModelNumber, value, + kSystemPropertyMaxLength); + if (result) { + os_arch_ = value; } build_time_ = __DATE__ " (" __TIME__ ")"; build_version_ = COBALT_VERSION; @@ -42,9 +54,16 @@ build_value->SetString("version", status.build_version_); scoped_ptr<base::DictionaryValue> os_value(new base::DictionaryValue()); - os_value->SetString("arch", status.os_arch_); - os_value->SetString("name", status.os_name_); - os_value->SetString("version", status.os_version_); + + if (status.os_arch_) { + os_value->SetString("arch", *status.os_arch_); + } + if (status.os_name_) { + os_value->SetString("name", *status.os_name_); + } + if (status.os_version_) { + os_value->SetString("version", *status.os_version_); + } scoped_ptr<base::DictionaryValue> status_value(new base::DictionaryValue()); status_value->Set("os", os_value.release());
diff --git a/src/cobalt/webdriver/protocol/server_status.h b/src/cobalt/webdriver/protocol/server_status.h index dcd825a..cdef9a4 100644 --- a/src/cobalt/webdriver/protocol/server_status.h +++ b/src/cobalt/webdriver/protocol/server_status.h
@@ -20,6 +20,7 @@ #include <string> #include "base/memory/scoped_ptr.h" +#include "base/optional.h" #include "base/values.h" namespace cobalt { @@ -36,9 +37,9 @@ static scoped_ptr<base::Value> ToValue(const ServerStatus& status); private: - std::string os_name_; - std::string os_arch_; - std::string os_version_; + base::optional<std::string> os_name_; + base::optional<std::string> os_arch_; + base::optional<std::string> os_version_; std::string build_time_; std::string build_version_; };
diff --git a/src/cobalt/webdriver/script_executor.cc b/src/cobalt/webdriver/script_executor.cc index ab7a9c8..cd3d63f 100644 --- a/src/cobalt/webdriver/script_executor.cc +++ b/src/cobalt/webdriver/script_executor.cc
@@ -16,40 +16,107 @@ #include "cobalt/webdriver/script_executor.h" +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/lazy_instance.h" +#include "base/path_service.h" +#include "cobalt/script/source_code.h" + namespace cobalt { namespace webdriver { +namespace { +// Path to the script to initialize the script execution harness. +const char kWebDriverInitScriptPath[] = "webdriver/webdriver-init.js"; + +// Wrapper around a scoped_refptr<script::SourceCode> instance. The script +// at kWebDriverInitScriptPath will be loaded from disk and a new +// script::SourceCode will be created. +class LazySourceLoader { + public: + LazySourceLoader() { + FilePath exe_path; + if (!PathService::Get(base::DIR_EXE, &exe_path)) { + NOTREACHED() << "Failed to get EXE path."; + return; + } + FilePath script_path = exe_path.Append(kWebDriverInitScriptPath); + std::string script_contents; + if (!file_util::ReadFileToString(script_path, &script_contents)) { + NOTREACHED() << "Failed to read script contents."; + return; + } + source_code_ = script::SourceCode::CreateSourceCode( + script_contents.c_str(), + base::SourceLocation(kWebDriverInitScriptPath, 1, 1)); + } + const scoped_refptr<script::SourceCode>& source_code() { + return source_code_; + } + + private: + scoped_refptr<script::SourceCode> source_code_; +}; + +// The script only needs to be loaded once, so allow it to persist as a +// LazyInstance and be shared amongst different WindowDriver instances. +base::LazyInstance<LazySourceLoader> lazy_source_loader = + LAZY_INSTANCE_INITIALIZER; +} // namespace + +void ScriptExecutor::LoadExecutorSourceCode() { lazy_source_loader.Get(); } + +scoped_refptr<ScriptExecutor> ScriptExecutor::Create( + ElementMapping* element_mapping, + const scoped_refptr<script::GlobalEnvironment>& global_environment) { + // This could be NULL if there was an error loading the harness source from + // disk. + scoped_refptr<script::SourceCode> source = + lazy_source_loader.Get().source_code(); + if (!source) { + return NULL; + } + + // Create a new ScriptExecutor and bind it to the global object. + scoped_refptr<ScriptExecutor> script_executor = + new ScriptExecutor(element_mapping); + global_environment->Bind("webdriverExecutor", script_executor); + + // Evaluate the harness initialization script. + std::string result; + if (!global_environment->EvaluateScript(source, &result)) { + return NULL; + } + + // The initialization script should have set this. + DCHECK(script_executor->execute_script_harness()); + return script_executor; +} + +bool ScriptExecutor::Execute( + const scoped_refptr<ScriptExecutorParams>& params, + ScriptExecutorResult::ResultHandler* result_handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + scoped_refptr<ScriptExecutorResult> executor_result( + new ScriptExecutorResult(result_handler)); + return ExecuteInternal(params, executor_result); +} void ScriptExecutor::set_execute_script_harness( const ExecuteFunctionCallbackHolder& callback) { DCHECK(thread_checker_.CalledOnValidThread()); - callback_.emplace(this, callback); + execute_callback_.emplace(this, callback); } const ScriptExecutor::ExecuteFunctionCallbackHolder* ScriptExecutor::execute_script_harness() { DCHECK(thread_checker_.CalledOnValidThread()); - if (callback_) { - return &(callback_->referenced_object()); + if (execute_callback_) { + return &(execute_callback_->referenced_object()); } else { return NULL; } } -base::optional<std::string> ScriptExecutor::Execute( - const script::OpaqueHandleHolder* function_object, - const std::string& json_arguments) { - DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(callback_); - - ExecuteFunctionCallback::ReturnValue callback_result = - callback_->value().Run(function_object, json_arguments); - if (callback_result.exception) { - return base::nullopt; - } else { - return callback_result.result; - } -} - scoped_refptr<dom::Element> ScriptExecutor::IdToElement(const std::string& id) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(element_mapping_); @@ -63,5 +130,16 @@ return element_mapping_->ElementToId(element).id(); } +bool ScriptExecutor::ExecuteInternal( + const scoped_refptr<ScriptExecutorParams>& params, + const scoped_refptr<ScriptExecutorResult>& result_handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(params); + DCHECK(result_handler); + ExecuteFunctionCallback::ReturnValue callback_result = + execute_callback_->value().Run(params, result_handler); + return !callback_result.exception; +} + } // namespace webdriver } // namespace cobalt
diff --git a/src/cobalt/webdriver/script_executor.h b/src/cobalt/webdriver/script_executor.h index 494cd5e..a46f1f2 100644 --- a/src/cobalt/webdriver/script_executor.h +++ b/src/cobalt/webdriver/script_executor.h
@@ -31,6 +31,8 @@ #include "cobalt/script/wrappable.h" #include "cobalt/webdriver/element_mapping.h" #include "cobalt/webdriver/protocol/element_id.h" +#include "cobalt/webdriver/script_executor_params.h" +#include "cobalt/webdriver/script_executor_result.h" namespace cobalt { namespace webdriver { @@ -40,14 +42,32 @@ public base::SupportsWeakPtr<ScriptExecutor>, public script::Wrappable { public: - typedef script::CallbackFunction<std::string( - const script::OpaqueHandleHolder*, const std::string&)> - ExecuteFunctionCallback; + typedef script::CallbackFunction<void( + const scoped_refptr<ScriptExecutorParams>&, + const scoped_refptr<ScriptExecutorResult>&)> ExecuteFunctionCallback; typedef script::ScriptObject<ExecuteFunctionCallback> ExecuteFunctionCallbackHolder; - explicit ScriptExecutor(ElementMapping* mapping) - : element_mapping_(mapping) {} + // This can be called on any thread to preload the webdriver javascript code. + // If this is not called, then it will be lazily loaded the first time a + // ScriptExecutor is created, which will be the Web Module thread in most + // cases. + static void LoadExecutorSourceCode(); + + // Create a new ScriptExecutor instance. + static scoped_refptr<ScriptExecutor> Create( + ElementMapping* element_mapping, + const scoped_refptr<script::GlobalEnvironment>& global_environment); + + // Calls the function set to the executeScriptHarness property with the + // provided |param|. The result of script execution will be passed to the + // caller through |result_handler|. + // Returns false on execution failure. + bool Execute(const scoped_refptr<ScriptExecutorParams>& params, + ScriptExecutorResult::ResultHandler* result_handler); + + // ScriptExecutor bindings implementation. + // void set_execute_script_harness( const ExecuteFunctionCallbackHolder& callback); @@ -56,19 +76,19 @@ scoped_refptr<dom::Element> IdToElement(const std::string& id); std::string ElementToId(const scoped_refptr<dom::Element>& id); - // Calls the function set to the executeScriptHarness property with the - // provided |function_body| and |json_arguments|. Returns a JSON - // serialized result string, of base::nullopt on execution failure. - base::optional<std::string> Execute( - const script::OpaqueHandleHolder* function_object, - const std::string& json_arguments); - DEFINE_WRAPPABLE_TYPE(ScriptExecutor); private: + explicit ScriptExecutor(ElementMapping* mapping) + : element_mapping_(mapping) {} + + bool ExecuteInternal( + const scoped_refptr<ScriptExecutorParams>& params, + const scoped_refptr<ScriptExecutorResult>& result_handler); + base::ThreadChecker thread_checker_; ElementMapping* element_mapping_; - base::optional<ExecuteFunctionCallbackHolder::Reference> callback_; + base::optional<ExecuteFunctionCallbackHolder::Reference> execute_callback_; }; } // namespace webdriver
diff --git a/src/cobalt/webdriver/script_executor_params.cc b/src/cobalt/webdriver/script_executor_params.cc new file mode 100644 index 0000000..d96a325 --- /dev/null +++ b/src/cobalt/webdriver/script_executor_params.cc
@@ -0,0 +1,50 @@ +/* + * Copyright 2016 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/webdriver/script_executor_params.h" + +#include "base/stringprintf.h" +#include "cobalt/script/source_code.h" + +namespace cobalt { +namespace webdriver { + +scoped_refptr<ScriptExecutorParams> ScriptExecutorParams::Create( + const scoped_refptr<script::GlobalEnvironment>& global_environment, + const std::string& function_body, const std::string& json_args, + base::optional<base::TimeDelta> async_timeout) { + scoped_refptr<ScriptExecutorParams> params(new ScriptExecutorParams()); + params->json_args_ = json_args; + + if (async_timeout) { + int async_timeout_ms = async_timeout->InMilliseconds(); + params->async_timeout_ = async_timeout_ms; + } + + std::string function = + StringPrintf("(function() {\n%s\n})", function_body.c_str()); + scoped_refptr<script::SourceCode> function_source = + script::SourceCode::CreateSourceCode( + function.c_str(), base::SourceLocation("[webdriver]", 1, 1)); + + if (!global_environment->EvaluateScript(function_source, params.get(), + ¶ms->function_object_)) { + DLOG(ERROR) << "Failed to create Function object"; + } + return params; +} +} // namespace webdriver +} // namespace cobalt
diff --git a/src/cobalt/webdriver/script_executor_params.h b/src/cobalt/webdriver/script_executor_params.h new file mode 100644 index 0000000..2256beb --- /dev/null +++ b/src/cobalt/webdriver/script_executor_params.h
@@ -0,0 +1,70 @@ +/* + * Copyright 2016 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_WEBDRIVER_SCRIPT_EXECUTOR_PARAMS_H_ +#define COBALT_WEBDRIVER_SCRIPT_EXECUTOR_PARAMS_H_ + +#if defined(ENABLE_WEBDRIVER) + +#include <string> + +#include "base/optional.h" +#include "base/threading/thread_checker.h" +#include "base/time.h" +#include "cobalt/script/global_environment.h" +#include "cobalt/script/opaque_handle.h" +#include "cobalt/script/wrappable.h" + +namespace cobalt { +namespace webdriver { + +// An instance of the ScriptExecutorResult is passed to the webdriver script +// execution harness to collect the results of running a script via webdriver. +// The results are forwarded to a ResultHandler instance. +class ScriptExecutorParams : public script::Wrappable { + public: + static scoped_refptr<ScriptExecutorParams> Create( + const scoped_refptr<script::GlobalEnvironment>& global_environment, + const std::string& function_body, const std::string& json_args) { + return Create(global_environment, function_body, json_args, base::nullopt); + } + + static scoped_refptr<ScriptExecutorParams> Create( + const scoped_refptr<script::GlobalEnvironment>& global_environment, + const std::string& function_body, const std::string& json_args, + base::optional<base::TimeDelta> async_timeout); + + const script::OpaqueHandleHolder* function_object() { + return function_object_ ? &function_object_->referenced_object() : NULL; + } + const std::string& json_args() { return json_args_; } + base::optional<int32_t> async_timeout() { return async_timeout_; } + + DEFINE_WRAPPABLE_TYPE(ScriptExecutorParams); + + private: + std::string function_body_; + base::optional<script::OpaqueHandleHolder::Reference> function_object_; + std::string json_args_; + base::optional<int32_t> async_timeout_; +}; + +} // namespace webdriver +} // namespace cobalt + +#endif // defined(ENABLE_WEBDRIVER) + +#endif // COBALT_WEBDRIVER_SCRIPT_EXECUTOR_PARAMS_H_
diff --git a/src/cobalt/webdriver/script_executor_result.h b/src/cobalt/webdriver/script_executor_result.h new file mode 100644 index 0000000..aa68d80 --- /dev/null +++ b/src/cobalt/webdriver/script_executor_result.h
@@ -0,0 +1,79 @@ +/* + * Copyright 2016 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_WEBDRIVER_SCRIPT_EXECUTOR_RESULT_H_ +#define COBALT_WEBDRIVER_SCRIPT_EXECUTOR_RESULT_H_ + +#if defined(ENABLE_WEBDRIVER) + +#include <string> + +#include "base/threading/thread_checker.h" +#include "cobalt/script/wrappable.h" + +namespace cobalt { +namespace webdriver { + +// An instance of the ScriptExecutorResult is passed to the webdriver script +// execution harness to collect the results of running a script via webdriver. +// The results are forwarded to a ResultHandler instance. +class ScriptExecutorResult : public script::Wrappable { + public: + // The ScriptExecutorResult class must only be accessed from the main thread, + // so use an instance of the ResultHandler class to access the results of + // execution from another thread. + // Exactly one of OnResult and OnTimeout will be called. After one of these + // is called, the ScriptExectutorResult object will no longer hold a pointer + // to the ResultHandler instance, so it's safe for the owning thread to + // destroy it. + class ResultHandler { + public: + virtual void OnResult(const std::string& result) = 0; + virtual void OnTimeout() = 0; + }; + + explicit ScriptExecutorResult(ScriptExecutorResult::ResultHandler* handler) + : result_handler_(handler) {} + + void OnResult(const std::string& result) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (result_handler_) { + result_handler_->OnResult(result); + result_handler_ = NULL; + } + } + + void OnTimeout() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (result_handler_) { + result_handler_->OnTimeout(); + result_handler_ = NULL; + } + } + + DEFINE_WRAPPABLE_TYPE(ScriptExecutorResult); + + private: + base::ThreadChecker thread_checker_; + ResultHandler* result_handler_; +}; + +} // namespace webdriver +} // namespace cobalt + +#endif // defined(ENABLE_WEBDRIVER) + +#endif // COBALT_WEBDRIVER_SCRIPT_EXECUTOR_RESULT_H_
diff --git a/src/cobalt/webdriver/server.cc b/src/cobalt/webdriver/server.cc index 6573cd7..9c69788 100644 --- a/src/cobalt/webdriver/server.cc +++ b/src/cobalt/webdriver/server.cc
@@ -176,13 +176,13 @@ }; } // namespace -WebDriverServer::WebDriverServer(int port, +WebDriverServer::WebDriverServer(int port, const std::string& listen_ip, const HandleRequestCallback& callback) : handle_request_callback_(callback) { - DLOG(INFO) << "Starting WebDriver server on port " << port; // Create http server - factory_.reset(new net::TCPListenSocketFactory("0.0.0.0", port)); + factory_.reset(new net::TCPListenSocketFactory(listen_ip, port)); server_ = new net::HttpServer(*factory_, this); + LOG(INFO) << "Starting WebDriver server on port " << port; } void WebDriverServer::OnHttpRequest(int connection_id,
diff --git a/src/cobalt/webdriver/server.h b/src/cobalt/webdriver/server.h index a35a947..b81008a 100644 --- a/src/cobalt/webdriver/server.h +++ b/src/cobalt/webdriver/server.h
@@ -79,7 +79,8 @@ HttpMethod, const std::string&, scoped_ptr<base::Value>, scoped_ptr<ResponseHandler>)> HandleRequestCallback; - WebDriverServer(int port, const HandleRequestCallback& callback); + WebDriverServer(int port, const std::string& listen_ip, + const HandleRequestCallback& callback); protected: // net::HttpServer::Delegate implementation.
diff --git a/src/cobalt/webdriver/session_driver.cc b/src/cobalt/webdriver/session_driver.cc index 91f3358..5a59853 100644 --- a/src/cobalt/webdriver/session_driver.cc +++ b/src/cobalt/webdriver/session_driver.cc
@@ -28,6 +28,9 @@ // Default page-load timeout. const int kPageLoadTimeoutInSeconds = 30; +// Max retries for the "can_retry" CommandResult case. +const int kMaxRetries = 5; + protocol::LogEntry::LogLevel SeverityToLogLevel(int severity) { switch (severity) { case logging::LOG_INFO: @@ -82,7 +85,11 @@ } util::CommandResult<void> SessionDriver::Navigate(const GURL& url) { - util::CommandResult<void> result = window_driver_->Navigate(url); + int retries = 0; + util::CommandResult<void> result; + do { + result = window_driver_->Navigate(url); + } while (result.can_retry() && (retries++ < kMaxRetries)); if (result.is_success()) { // TODO: Use timeout as specified by the webdriver client. wait_for_navigation_.Run(
diff --git a/src/cobalt/webdriver/testing/stub_window.h b/src/cobalt/webdriver/testing/stub_window.h new file mode 100644 index 0000000..aa6c912 --- /dev/null +++ b/src/cobalt/webdriver/testing/stub_window.h
@@ -0,0 +1,93 @@ +/* + * Copyright 2016 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_WEBDRIVER_TESTING_STUB_WINDOW_H_ +#define COBALT_WEBDRIVER_TESTING_STUB_WINDOW_H_ + +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/message_loop.h" +#include "cobalt/css_parser/parser.h" +#include "cobalt/dom/local_storage_database.h" +#include "cobalt/dom/window.h" +#include "cobalt/dom_parser/parser.h" +#include "cobalt/loader/fetcher_factory.h" +#include "cobalt/media/media_module_stub.h" +#include "cobalt/network/network_module.h" +#include "googleurl/src/gurl.h" + +namespace cobalt { +namespace webdriver { +namespace testing { + +// A helper class for WebDriver tests that brings up a dom::Window with a number +// of parts stubbed out. +class StubWindow { + public: + StubWindow() + : message_loop_(MessageLoop::TYPE_DEFAULT), + css_parser_(css_parser::Parser::Create()), + dom_parser_(new dom_parser::Parser(base::Bind(&StubErrorCallback))), + fetcher_factory_(new loader::FetcherFactory(&network_module_)), + local_storage_database_(NULL), + stub_media_module_(new media::MediaModuleStub()), + url_("about:blank"), + dom_stat_tracker_(new dom::DomStatTracker("StubWindow")) { + engine_ = script::JavaScriptEngine::CreateEngine(); + global_environment_ = engine_->CreateGlobalEnvironment(); + window_ = new dom::Window( + 1920, 1080, css_parser_.get(), dom_parser_.get(), + fetcher_factory_.get(), NULL, NULL, NULL, NULL, + &local_storage_database_, stub_media_module_.get(), + stub_media_module_.get(), NULL, NULL, NULL, dom_stat_tracker_.get(), + url_, "", "en-US", base::Callback<void(const GURL&)>(), + base::Bind(&StubErrorCallback), NULL, network_bridge::PostSender(), + std::string() /* default security policy */, dom::kCspEnforcementEnable, + base::Closure() /* csp_policy_changed */, + base::Closure() /* window_close */); + global_environment_->CreateGlobalObject(window_, &environment_settings_); + } + + scoped_refptr<dom::Window> window() { return window_; } + scoped_refptr<script::GlobalEnvironment> global_environment() { + return global_environment_; + } + + private: + static void StubErrorCallback(const std::string& /*error*/) {} + + MessageLoop message_loop_; + scoped_ptr<css_parser::Parser> css_parser_; + scoped_ptr<dom_parser::Parser> dom_parser_; + network::NetworkModule network_module_; + scoped_ptr<loader::FetcherFactory> fetcher_factory_; + dom::LocalStorageDatabase local_storage_database_; + scoped_ptr<media::MediaModule> stub_media_module_; + GURL url_; + scoped_ptr<dom::DomStatTracker> dom_stat_tracker_; + script::EnvironmentSettings environment_settings_; + scoped_ptr<script::JavaScriptEngine> engine_; + scoped_refptr<script::GlobalEnvironment> global_environment_; + scoped_refptr<dom::Window> window_; +}; + +} // namespace testing +} // namespace webdriver +} // namespace cobalt + +#endif // COBALT_WEBDRIVER_TESTING_STUB_WINDOW_H_
diff --git a/src/cobalt/webdriver/util/call_on_message_loop.h b/src/cobalt/webdriver/util/call_on_message_loop.h index 8b28a82..c2a19d2 100644 --- a/src/cobalt/webdriver/util/call_on_message_loop.h +++ b/src/cobalt/webdriver/util/call_on_message_loop.h
@@ -35,26 +35,63 @@ CallOnMessageLoopHelper( const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy, const CallbackType& callback) - : completed_event_(false, false) { + : completed_event_(true, false), success_(false) { DCHECK_NE(base::MessageLoopProxy::current(), message_loop_proxy); + scoped_ptr<DeletionSignaler> dt(new DeletionSignaler(&completed_event_)); + // Note that while MessageLoopProxy::PostTask returns false + // after the message loop has gone away, it still can return true + // even if tasks are posted during shutdown and will never be run, + // so we ignore this return value. message_loop_proxy->PostTask( - FROM_HERE, base::Bind(&CallOnMessageLoopHelper::CallAndSignal, - base::Unretained(this), callback)); + FROM_HERE, + base::Bind(&CallOnMessageLoopHelper::Call, base::Unretained(this), + callback, base::Passed(&dt))); } - ReturnValue WaitForResult() { + // Waits for result, filling |out| with the return value if successful. + // Returns true on success or false if the message loop went away + // before the task was executed. + bool WaitForResult(ReturnValue* out) { completed_event_.Wait(); - return result_; + if (success_) { + *out = result_; + } + return success_; + } + + ~CallOnMessageLoopHelper() { + // We must ensure that we've waited for completion otherwise + // DeletionSignaler will have a use-after-free. + completed_event_.Wait(); } private: - void CallAndSignal(const CallbackType& callback) { + // DeletionSignaler signals an event when the destructor is called. + // This allows us to use the base::Passed mechanism to signal our + // completed_event_ both when Call() has been invoked and when + // the message loop has been deleted. + class DeletionSignaler { + public: + base::WaitableEvent* to_signal_; + + explicit DeletionSignaler(base::WaitableEvent* to_signal) + : to_signal_(to_signal) {} + + ~DeletionSignaler() { to_signal_->Signal(); } + + private: + DISALLOW_COPY_AND_ASSIGN(DeletionSignaler); + }; + + void Call(const CallbackType& callback, + scoped_ptr<DeletionSignaler> dt ALLOW_UNUSED) { result_ = callback.Run(); - completed_event_.Signal(); + success_ = true; } base::WaitableEvent completed_event_; ReturnValue result_; + bool success_; }; // Used with CallWeakOnMessageLoop. @@ -71,14 +108,37 @@ } // namespace internal // Call the base::Callback on the specified message loop and wait for it to -// complete, then return the result of the callback. +// complete. Returns true if successful, or false if the underlying +// PostTask failed. This can happen if a WebModule shuts down due to a page +// navigation. +// +// On success, |out| is set to the result. template <class ReturnValue> -ReturnValue CallOnMessageLoop( +bool TryCallOnMessageLoop( const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy, - const base::Callback<ReturnValue(void)>& callback) { + const base::Callback<ReturnValue(void)>& callback, ReturnValue* out) { internal::CallOnMessageLoopHelper<ReturnValue> call_helper(message_loop_proxy, callback); - return call_helper.WaitForResult(); + return call_helper.WaitForResult(out); +} + +// Tries to call |callback| on messageloop |message_loop_proxy|, +// but returns a CommandResult of |window_disappeared_code| if the +// message loop has shut down. This can happen if a WebModule shuts +// down due to a page navigation. +template <typename ReturnValue> +util::CommandResult<ReturnValue> CallOnMessageLoop( + const scoped_refptr<base::MessageLoopProxy>& message_loop_proxy, + const base::Callback<util::CommandResult<ReturnValue>(void)>& callback, + protocol::Response::StatusCode window_disappeared_code) { + util::CommandResult<ReturnValue> result; + bool success = TryCallOnMessageLoop(message_loop_proxy, callback, &result); + + if (!success) { + result = + util::CommandResult<ReturnValue>(window_disappeared_code, "", true); + } + return result; } // Supports a common pattern in the various XXXDriver classes. @@ -97,10 +157,11 @@ protocol::Response::StatusCode no_such_object_code) { typedef util::CommandResult<ReturnValue> CommandResult; typedef base::optional<ReturnValue> InternalResult; - InternalResult result = util::CallOnMessageLoop( + InternalResult result; + bool success = util::TryCallOnMessageLoop( message_loop, - base::Bind(&internal::RunWeak<T, ReturnValue>, get_weak, cb)); - if (result) { + base::Bind(&internal::RunWeak<T, ReturnValue>, get_weak, cb), &result); + if (success && result) { return CommandResult(result.value()); } else { return CommandResult(no_such_object_code);
diff --git a/src/cobalt/webdriver/util/command_result.h b/src/cobalt/webdriver/util/command_result.h index d1254c7..8944d4d 100644 --- a/src/cobalt/webdriver/util/command_result.h +++ b/src/cobalt/webdriver/util/command_result.h
@@ -32,12 +32,17 @@ public: CommandResult() : status_code_(protocol::Response::kUnknownError) {} explicit CommandResult(protocol::Response::StatusCode status) - : status_code_(status) {} + : status_code_(status), can_retry_(false) {} CommandResult(protocol::Response::StatusCode status, const std::string& message) - : status_code_(status), error_message_(message) {} + : status_code_(status), error_message_(message), can_retry_(false) {} + CommandResult(protocol::Response::StatusCode status, + const std::string& message, bool can_retry) + : status_code_(status), error_message_(message), can_retry_(can_retry) {} explicit CommandResult(const T& result) - : status_code_(protocol::Response::kSuccess), result_(result) {} + : status_code_(protocol::Response::kSuccess), + result_(result), + can_retry_(false) {} bool is_success() const { return status_code_ == protocol::Response::kSuccess; @@ -45,11 +50,16 @@ protocol::Response::StatusCode status_code() const { return status_code_; } const T& result() const { return result_.value(); } std::string error_message() const { return error_message_.value_or(""); } + // Returns true if this result occurred due to a transient issue, + // such as the WebModule going away. Retrying w/ a new WebModule + // may succeed. + bool can_retry() const { return can_retry_; } private: protocol::Response::StatusCode status_code_; base::optional<T> result_; base::optional<std::string> error_message_; + bool can_retry_; }; template <> @@ -57,20 +67,28 @@ public: CommandResult() : status_code_(protocol::Response::kUnknownError) {} explicit CommandResult(protocol::Response::StatusCode status) - : status_code_(status) {} + : status_code_(status), can_retry_(false) {} CommandResult(protocol::Response::StatusCode status, const std::string& message) - : status_code_(status), error_message_(message) {} + : status_code_(status), error_message_(message), can_retry_(false) {} + CommandResult(protocol::Response::StatusCode status, + const std::string& message, bool can_retry) + : status_code_(status), error_message_(message), can_retry_(can_retry) {} bool is_success() const { return status_code_ == protocol::Response::kSuccess; } protocol::Response::StatusCode status_code() const { return status_code_; } std::string error_message() const { return error_message_.value_or(""); } + // Returns true if this result occurred due to a transient issue, + // such as the WebModule going away. Retrying w/ a new WebModule + // may succeed. + bool can_retry() const { return can_retry_; } private: protocol::Response::StatusCode status_code_; base::optional<std::string> error_message_; + bool can_retry_; }; } // namespace util
diff --git a/src/cobalt/webdriver/util/dispatch_command_factory.h b/src/cobalt/webdriver/util/dispatch_command_factory.h index 1e45e4b..cfed299 100644 --- a/src/cobalt/webdriver/util/dispatch_command_factory.h +++ b/src/cobalt/webdriver/util/dispatch_command_factory.h
@@ -122,6 +122,9 @@ template <class DriverClassT> class DispatchCommandFactory : public base::RefCounted<DispatchCommandFactory<DriverClassT> > { + // Max retries for the "can_retry" CommandResult case. + static const int kMaxRetries = 5; + public: // Typedef'd for less verbose code. typedef WebDriverDispatcher::CommandResultHandler CommandResultHandler; @@ -220,7 +223,11 @@ DriverClassT* driver, const base::Value* parameters, CommandResultHandler* result_handler) { // Ignore parameters. - util::CommandResult<R> command_result = driver_command.Run(driver); + int retries = 0; + util::CommandResult<R> command_result; + do { + command_result = driver_command.Run(driver); + } while (command_result.can_retry() && (retries++ < kMaxRetries)); internal::ReturnResponse(session_id, command_result, result_handler); } @@ -245,8 +252,11 @@ result_handler->SendInvalidRequestResponse( WebDriverDispatcher::CommandResultHandler::kInvalidParameters, ""); } else { - util::CommandResult<R> command_result = - driver_command.Run(driver, param.value()); + int retries = 0; + util::CommandResult<R> command_result; + do { + command_result = driver_command.Run(driver, param.value()); + } while (command_result.can_retry() && (retries++ < kMaxRetries)); internal::ReturnResponse(session_id, command_result, result_handler); } }
diff --git a/src/cobalt/webdriver/web_driver_module.cc b/src/cobalt/webdriver/web_driver_module.cc index 46a77e8..9b4b9d0 100644 --- a/src/cobalt/webdriver/web_driver_module.cc +++ b/src/cobalt/webdriver/web_driver_module.cc
@@ -159,8 +159,11 @@ } // namespace +const char WebDriverModule::kDefaultListenIp[] = "0.0.0.0"; + WebDriverModule::WebDriverModule( - int server_port, const CreateSessionDriverCB& create_session_driver_cb, + int server_port, const std::string& listen_ip, + const CreateSessionDriverCB& create_session_driver_cb, const GetScreenshotFunction& get_screenshot_function, const SetProxyFunction& set_proxy_function, const base::Closure& shutdown_cb) @@ -294,6 +297,11 @@ base::Bind(&WindowDriver::Execute))); webdriver_dispatcher_->RegisterCommand( WebDriverServer::kPost, + StringPrintf("/session/%s/execute_async", kSessionIdVariable), + current_window_command_factory->GetCommandHandler( + base::Bind(&WindowDriver::ExecuteAsync))); + webdriver_dispatcher_->RegisterCommand( + WebDriverServer::kPost, StringPrintf("/session/%s/element", kSessionIdVariable), current_window_command_factory->GetCommandHandler( base::Bind(&WindowDriver::FindElement))); @@ -400,7 +408,7 @@ base::Thread::Options(MessageLoop::TYPE_IO, 0)); webdriver_thread_.message_loop()->PostTask( FROM_HERE, base::Bind(&WebDriverModule::StartServer, - base::Unretained(this), server_port)); + base::Unretained(this), server_port, listen_ip)); } WebDriverModule::~WebDriverModule() { @@ -423,11 +431,12 @@ } } -void WebDriverModule::StartServer(int server_port) { +void WebDriverModule::StartServer(int server_port, + const std::string& listen_ip) { DCHECK(thread_checker_.CalledOnValidThread()); // Create a new WebDriverServer and pass in the Dispatcher. webdriver_server_.reset(new WebDriverServer( - server_port, + server_port, listen_ip, base::Bind(&WebDriverDispatcher::HandleWebDriverServerRequest, base::Unretained(webdriver_dispatcher_.get())))); }
diff --git a/src/cobalt/webdriver/web_driver_module.h b/src/cobalt/webdriver/web_driver_module.h index 2df30dd..b0c89ed 100644 --- a/src/cobalt/webdriver/web_driver_module.h +++ b/src/cobalt/webdriver/web_driver_module.h
@@ -48,7 +48,10 @@ typedef base::Callback<void(const ScreenshotCompleteCallback&)> GetScreenshotFunction; typedef base::Callback<void(const std::string&)> SetProxyFunction; - WebDriverModule(int server_port, + // Use this as the default listen_ip. It means "any interface on the local + // machine" eg INADDR_ANY. + static const char kDefaultListenIp[]; + WebDriverModule(int server_port, const std::string& listen_ip, const CreateSessionDriverCB& create_session_driver_cb, const GetScreenshotFunction& get_screenshot_function, const SetProxyFunction& set_proxy_function, @@ -65,7 +68,7 @@ void OnWindowRecreated(); private: - void StartServer(int server_port); + void StartServer(int server_port, const std::string& listen_ip); void StopServer(); void GetServerStatus( const base::Value* parameters,
diff --git a/src/cobalt/webdriver/webdriver.gyp b/src/cobalt/webdriver/webdriver.gyp index 558a0ee..1463a01 100644 --- a/src/cobalt/webdriver/webdriver.gyp +++ b/src/cobalt/webdriver/webdriver.gyp
@@ -61,6 +61,9 @@ 'protocol/window_id.h', 'script_executor.cc', 'script_executor.h', + 'script_executor_params.cc', + 'script_executor_params.h', + 'script_executor_result.h', 'search.cc', 'search.h', 'server.cc', @@ -86,53 +89,7 @@ ], }, - { - 'target_name': 'webdriver_test', - 'type': '<(gtest_target_type)', - 'conditions': [ - ['enable_webdriver==1', { - 'sources': [ - 'get_element_text_test.cc', - 'is_displayed_test.cc', - 'keyboard_test.cc', - ], - }], - ], - 'dependencies': [ - '<(DEPTH)/base/base.gyp:run_all_unittests', - '<(DEPTH)/cobalt/base/base.gyp:base', - '<(DEPTH)/cobalt/css_parser/css_parser.gyp:css_parser', - '<(DEPTH)/cobalt/dom/dom.gyp:dom', - '<(DEPTH)/cobalt/dom_parser/dom_parser.gyp:dom_parser', - '<(DEPTH)/testing/gmock.gyp:gmock', - '<(DEPTH)/testing/gtest.gyp:gtest', - 'webdriver', - ], - 'actions': [ - { - 'action_name': 'webdriver_test_copy_test_data', - 'variables': { - 'input_files': [ - '<(DEPTH)/cobalt/webdriver/testdata/', - ], - 'output_dir': 'cobalt/webdriver_test', - }, - 'includes': ['../build/copy_test_data.gypi'], - } - ], - }, - { - 'target_name': 'webdriver_test_deploy', - 'type': 'none', - 'dependencies': [ - 'webdriver_test', - ], - 'variables': { - 'executable_name': 'webdriver_test', - }, - 'includes': [ '../../starboard/build/deploy.gypi' ], - }, { 'target_name': 'copy_webdriver_data',
diff --git a/src/cobalt/webdriver/webdriver_test.gyp b/src/cobalt/webdriver/webdriver_test.gyp new file mode 100644 index 0000000..d01bef5 --- /dev/null +++ b/src/cobalt/webdriver/webdriver_test.gyp
@@ -0,0 +1,48 @@ +{ + 'targets': [ + { + 'target_name': 'webdriver_test', + 'type': '<(gtest_target_type)', + 'conditions': [ + ['enable_webdriver==1', { + 'sources': [ + 'get_element_text_test.cc', + 'is_displayed_test.cc', + 'keyboard_test.cc', + 'execute_test.cc', + ], + }], + ], + 'dependencies': [ + '<(DEPTH)/base/base.gyp:run_all_unittests', + '<(DEPTH)/cobalt/browser/browser.gyp:browser', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + 'actions': [ + { + 'action_name': 'webdriver_test_copy_test_data', + 'variables': { + 'input_files': [ + '<(DEPTH)/cobalt/webdriver/testdata/', + ], + 'output_dir': 'cobalt/webdriver_test', + }, + 'includes': ['../build/copy_test_data.gypi'], + } + ], + }, + + { + 'target_name': 'webdriver_test_deploy', + 'type': 'none', + 'dependencies': [ + 'webdriver_test', + ], + 'variables': { + 'executable_name': 'webdriver_test', + }, + 'includes': [ '../../starboard/build/deploy.gypi' ], + }, + ] +}
diff --git a/src/cobalt/webdriver/window_driver.cc b/src/cobalt/webdriver/window_driver.cc index 3e77e54..b23abc2 100644 --- a/src/cobalt/webdriver/window_driver.cc +++ b/src/cobalt/webdriver/window_driver.cc
@@ -18,16 +18,10 @@ #include <utility> -#include "base/file_path.h" -#include "base/file_util.h" -#include "base/lazy_instance.h" -#include "base/path_service.h" -#include "base/stringprintf.h" #include "base/synchronization/waitable_event.h" #include "cobalt/dom/document.h" #include "cobalt/dom/location.h" #include "cobalt/script/global_environment.h" -#include "cobalt/script/source_code.h" #include "cobalt/webdriver/keyboard.h" #include "cobalt/webdriver/search.h" #include "cobalt/webdriver/util/call_on_message_loop.h" @@ -35,87 +29,45 @@ namespace cobalt { namespace webdriver { namespace { -// Path to the script to initialize the script execution harness. -const char kWebDriverInitScriptPath[] = "webdriver/webdriver-init.js"; -// Wrapper around a scoped_refptr<script::SourceCode> instance. The script -// at kWebDriverInitScriptPath will be loaded from disk and a new -// script::SourceCode will be created. -class LazySourceLoader { +class SyncExecuteResultHandler : public ScriptExecutorResult::ResultHandler { public: - LazySourceLoader() { - FilePath exe_path; - if (!PathService::Get(base::DIR_EXE, &exe_path)) { - NOTREACHED() << "Failed to get EXE path."; - return; - } - FilePath script_path = exe_path.Append(kWebDriverInitScriptPath); - std::string script_contents; - if (!file_util::ReadFileToString(script_path, &script_contents)) { - NOTREACHED() << "Failed to read script contents."; - return; - } - source_code_ = script::SourceCode::CreateSourceCode( - script_contents.c_str(), - base::SourceLocation(kWebDriverInitScriptPath, 1, 1)); + void OnResult(const std::string& result) OVERRIDE { + DCHECK(!result_); + result_ = result; } - const scoped_refptr<script::SourceCode>& source_code() { - return source_code_; + void OnTimeout() OVERRIDE { NOTREACHED(); } + std::string result() const { + DCHECK(result_); + return result_.value_or(std::string()); } private: - scoped_refptr<script::SourceCode> source_code_; + base::optional<std::string> result_; }; -// The script only needs to be loaded once, so allow it to persist as a -// LazyInstance and be shared amongst different WindowDriver instances. -base::LazyInstance<LazySourceLoader> lazy_source_loader = - LAZY_INSTANCE_INITIALIZER; +class AsyncExecuteResultHandler : public ScriptExecutorResult::ResultHandler { + public: + AsyncExecuteResultHandler() : timed_out_(false), event_(true, false) {} -scoped_refptr<ScriptExecutor> CreateScriptExecutor( - ElementMapping* element_mapping, - const scoped_refptr<script::GlobalEnvironment>& global_environment) { - // This could be NULL if there was an error loading the harness source from - // disk. - scoped_refptr<script::SourceCode> source = - lazy_source_loader.Get().source_code(); - if (!source) { - return NULL; + void WaitForResult() { event_.Wait(); } + bool timed_out() const { return timed_out_; } + const std::string& result() const { return result_; } + + private: + void OnResult(const std::string& result) OVERRIDE { + result_ = result; + event_.Signal(); + } + void OnTimeout() OVERRIDE { + timed_out_ = true; + event_.Signal(); } - // Create a new ScriptExecutor and bind it to the global object. - scoped_refptr<ScriptExecutor> script_executor = - new ScriptExecutor(element_mapping); - global_environment->Bind("webdriverExecutor", script_executor); - - // Evaluate the harness initialization script. - std::string result; - if (!global_environment->EvaluateScript(source, &result)) { - return NULL; - } - - // The initialization script should have set this. - DCHECK(script_executor->execute_script_harness()); - return script_executor; -} - -void CreateFunction( - const std::string& function_body, - const scoped_refptr<script::GlobalEnvironment>& global_environment, - scoped_refptr<ScriptExecutor> script_executor, - base::optional<script::OpaqueHandleHolder::Reference>* out_opaque_handle) { - std::string function = - StringPrintf("(function() {\n%s\n})", function_body.c_str()); - scoped_refptr<script::SourceCode> function_source = - script::SourceCode::CreateSourceCode( - function.c_str(), base::SourceLocation("[webdriver]", 1, 1)); - - if (!global_environment->EvaluateScript( - function_source, make_scoped_refptr(script_executor.get()), - out_opaque_handle)) { - DLOG(ERROR) << "Failed to create Function object"; - } -} + std::string result_; + bool timed_out_; + base::WaitableEvent event_; +}; std::string GetCurrentUrl(dom::Window* window) { DCHECK(window); @@ -158,10 +110,12 @@ const protocol::WindowId& window_id, const base::WeakPtr<dom::Window>& window, const GetGlobalEnvironmentFunction& get_global_environment_function, + KeyboardEventInjector keyboard_injector, const scoped_refptr<base::MessageLoopProxy>& message_loop) : window_id_(window_id), window_(window), get_global_environment_(get_global_environment_function), + keyboard_injector_(keyboard_injector), window_message_loop_(message_loop), element_driver_map_deleter_(&element_drivers_), next_element_id_(0) { @@ -182,9 +136,12 @@ // It's expected that the WebDriver thread is the only other thread to call // this function. DCHECK(thread_checker_.CalledOnValidThread()); - return util::CallOnMessageLoop(window_message_loop_, - base::Bind(&WindowDriver::GetElementDriver, base::Unretained(this), - element_id)); + ElementDriver* result; + bool success = util::TryCallOnMessageLoop( + window_message_loop_, base::Bind(&WindowDriver::GetElementDriver, + base::Unretained(this), element_id), + &result); + return success ? result : NULL; } DCHECK_EQ(base::MessageLoopProxy::current(), window_message_loop_); ElementDriverMap::iterator it = element_drivers_.find(element_id.id()); @@ -207,7 +164,8 @@ DCHECK(thread_checker_.CalledOnValidThread()); return util::CallOnMessageLoop( window_message_loop_, - base::Bind(&WindowDriver::NavigateInternal, base::Unretained(this), url)); + base::Bind(&WindowDriver::NavigateInternal, base::Unretained(this), url), + protocol::Response::kNoSuchWindow); } util::CommandResult<std::string> WindowDriver::GetCurrentUrl() { @@ -232,17 +190,21 @@ const protocol::SearchStrategy& strategy) { DCHECK(thread_checker_.CalledOnValidThread()); - return util::CallOnMessageLoop(window_message_loop_, + return util::CallOnMessageLoop( + window_message_loop_, base::Bind(&WindowDriver::FindElementsInternal<protocol::ElementId>, - base::Unretained(this), strategy)); + base::Unretained(this), strategy), + protocol::Response::kNoSuchElement); } util::CommandResult<std::vector<protocol::ElementId> > WindowDriver::FindElements(const protocol::SearchStrategy& strategy) { DCHECK(thread_checker_.CalledOnValidThread()); - return util::CallOnMessageLoop(window_message_loop_, + return util::CallOnMessageLoop( + window_message_loop_, base::Bind(&WindowDriver::FindElementsInternal<ElementIdVector>, - base::Unretained(this), strategy)); + base::Unretained(this), strategy), + protocol::Response::kNoSuchElement); } util::CommandResult<std::string> WindowDriver::GetSource() { @@ -256,12 +218,53 @@ util::CommandResult<protocol::ScriptResult> WindowDriver::Execute( const protocol::Script& script) { + typedef util::CommandResult<protocol::ScriptResult> CommandResult; DCHECK(thread_checker_.CalledOnValidThread()); - // Poke the lazy loader so we don't hit the disk on window_message_loop_. - lazy_source_loader.Get(); - return util::CallOnMessageLoop( - window_message_loop_, base::Bind(&WindowDriver::ExecuteScriptInternal, - base::Unretained(this), script)); + // Pre-load the ScriptExecutor source so we don't hit the disk on + // window_message_loop_. + ScriptExecutor::LoadExecutorSourceCode(); + + SyncExecuteResultHandler result_handler; + + CommandResult result = util::CallOnMessageLoop( + window_message_loop_, + base::Bind(&WindowDriver::ExecuteScriptInternal, base::Unretained(this), + script, base::nullopt, &result_handler), + protocol::Response::kNoSuchWindow); + if (result.is_success()) { + return CommandResult(protocol::ScriptResult(result_handler.result())); + } else { + return result; + } +} + +util::CommandResult<protocol::ScriptResult> WindowDriver::ExecuteAsync( + const protocol::Script& script) { + typedef util::CommandResult<protocol::ScriptResult> CommandResult; + DCHECK(thread_checker_.CalledOnValidThread()); + // Pre-load the ScriptExecutor source so we don't hit the disk on + // window_message_loop_. + ScriptExecutor::LoadExecutorSourceCode(); + + const base::TimeDelta kDefaultAsyncTimeout = + base::TimeDelta::FromMilliseconds(0); + AsyncExecuteResultHandler result_handler; + CommandResult result = util::CallOnMessageLoop( + window_message_loop_, + base::Bind(&WindowDriver::ExecuteScriptInternal, base::Unretained(this), + script, kDefaultAsyncTimeout, &result_handler), + protocol::Response::kNoSuchWindow); + + if (!result.is_success()) { + return result; + } + + result_handler.WaitForResult(); + if (result_handler.timed_out()) { + return CommandResult(protocol::Response::kTimeOut); + } else { + return CommandResult(protocol::ScriptResult(result_handler.result())); + } } util::CommandResult<void> WindowDriver::SendKeys(const protocol::Keys& keys) { @@ -274,13 +277,15 @@ return util::CallOnMessageLoop( window_message_loop_, base::Bind(&WindowDriver::SendKeysInternal, base::Unretained(this), - base::Passed(&events))); + base::Passed(&events)), + protocol::Response::kNoSuchWindow); } util::CommandResult<protocol::ElementId> WindowDriver::GetActiveElement() { return util::CallOnMessageLoop( window_message_loop_, base::Bind(&WindowDriver::GetActiveElementInternal, - base::Unretained(this))); + base::Unretained(this)), + protocol::Response::kNoSuchWindow); } util::CommandResult<void> WindowDriver::SwitchFrame( @@ -327,7 +332,8 @@ DCHECK(thread_checker_.CalledOnValidThread()); return util::CallOnMessageLoop(window_message_loop_, base::Bind(&WindowDriver::AddCookieInternal, - base::Unretained(this), cookie)); + base::Unretained(this), cookie), + protocol::Response::kNoSuchWindow); } protocol::ElementId WindowDriver::ElementToId( @@ -351,6 +357,7 @@ std::pair<ElementDriverMapIt, bool> pair_it = element_drivers_.insert(std::make_pair( element_id.id(), new ElementDriver(element_id, weak_element, this, + keyboard_injector_, window_message_loop_))); DCHECK(pair_it.second) << "An ElementDriver was already mapped to the element id: " @@ -373,7 +380,9 @@ } util::CommandResult<protocol::ScriptResult> WindowDriver::ExecuteScriptInternal( - const protocol::Script& script) { + const protocol::Script& script, + base::optional<base::TimeDelta> async_timeout, + ScriptExecutorResult::ResultHandler* async_handler) { typedef util::CommandResult<protocol::ScriptResult> CommandResult; DCHECK_EQ(base::MessageLoopProxy::current(), window_message_loop_); if (!window_) { @@ -390,7 +399,7 @@ // global object, thus with the WindowDriver. if (!script_executor_) { scoped_refptr<ScriptExecutor> script_executor = - CreateScriptExecutor(this, global_environment); + ScriptExecutor::Create(this, global_environment); if (!script_executor) { DLOG(INFO) << "Failed to create ScriptExecutor."; return CommandResult(protocol::Response::kUnknownError); @@ -401,24 +410,20 @@ DLOG(INFO) << "Executing: " << script.function_body(); DLOG(INFO) << "Arguments: " << script.argument_array(); - base::optional<script::OpaqueHandleHolder::Reference> function_object; - CreateFunction(script.function_body(), global_environment, - make_scoped_refptr(script_executor_.get()), &function_object); - if (!function_object) { - return CommandResult(protocol::Response::kJavaScriptError); - } + scoped_refptr<ScriptExecutorParams> params = + ScriptExecutorParams::Create(global_environment, script.function_body(), + script.argument_array(), async_timeout); - base::optional<std::string> script_result = script_executor_->Execute( - &(function_object->referenced_object()), script.argument_array()); - if (script_result) { - return CommandResult(protocol::ScriptResult(script_result.value())); - } else { - return CommandResult(protocol::Response::kJavaScriptError); + if (params->function_object()) { + if (script_executor_->Execute(params, async_handler)) { + return CommandResult(protocol::Response::kSuccess); + } } + return CommandResult(protocol::Response::kJavaScriptError); } util::CommandResult<void> WindowDriver::SendKeysInternal( - scoped_ptr<KeyboardEventVector> events) { + scoped_ptr<Keyboard::KeyboardEventVector> events) { typedef util::CommandResult<void> CommandResult; DCHECK_EQ(base::MessageLoopProxy::current(), window_message_loop_); if (!window_) { @@ -426,8 +431,7 @@ } for (size_t i = 0; i < events->size(); ++i) { - // InjectEvent will send to the focused element. - window_->InjectEvent((*events)[i]); + keyboard_injector_.Run(scoped_refptr<dom::Element>(), (*events)[i]); } return CommandResult(protocol::Response::kSuccess); }
diff --git a/src/cobalt/webdriver/window_driver.h b/src/cobalt/webdriver/window_driver.h index ae03a9f..e68f06d 100644 --- a/src/cobalt/webdriver/window_driver.h +++ b/src/cobalt/webdriver/window_driver.h
@@ -34,6 +34,7 @@ #include "cobalt/dom/window.h" #include "cobalt/webdriver/element_driver.h" #include "cobalt/webdriver/element_mapping.h" +#include "cobalt/webdriver/keyboard.h" #include "cobalt/webdriver/protocol/cookie.h" #include "cobalt/webdriver/protocol/frame_id.h" #include "cobalt/webdriver/protocol/keys.h" @@ -54,11 +55,15 @@ // will map to a method on this class. class WindowDriver : private ElementMapping { public: + typedef base::Callback<void(scoped_refptr<dom::Element>, + const dom::KeyboardEvent::Data&)> + KeyboardEventInjector; typedef base::Callback<scoped_refptr<script::GlobalEnvironment>()> GetGlobalEnvironmentFunction; WindowDriver(const protocol::WindowId& window_id, const base::WeakPtr<dom::Window>& window, const GetGlobalEnvironmentFunction& get_global_environment, + KeyboardEventInjector keyboard_injector, const scoped_refptr<base::MessageLoopProxy>& message_loop); ~WindowDriver(); const protocol::WindowId& window_id() { return window_id_; } @@ -77,6 +82,8 @@ util::CommandResult<std::string> GetSource(); util::CommandResult<protocol::ScriptResult> Execute( const protocol::Script& script); + util::CommandResult<protocol::ScriptResult> ExecuteAsync( + const protocol::Script& script); util::CommandResult<void> SendKeys(const protocol::Keys& keys); util::CommandResult<protocol::ElementId> GetActiveElement(); util::CommandResult<void> SwitchFrame(const protocol::FrameId& frame_id); @@ -89,7 +96,6 @@ typedef base::hash_map<std::string, ElementDriver*> ElementDriverMap; typedef ElementDriverMap::iterator ElementDriverMapIt; typedef std::vector<protocol::ElementId> ElementIdVector; - typedef std::vector<scoped_refptr<dom::KeyboardEvent> > KeyboardEventVector; // ScriptExecutor::ElementMapping implementation. protocol::ElementId ElementToId( @@ -113,10 +119,12 @@ const protocol::SearchStrategy& strategy); util::CommandResult<protocol::ScriptResult> ExecuteScriptInternal( - const protocol::Script& script); + const protocol::Script& script, + base::optional<base::TimeDelta> async_timeout, + ScriptExecutorResult::ResultHandler* result_handler); util::CommandResult<void> SendKeysInternal( - scoped_ptr<KeyboardEventVector> keyboard_events); + scoped_ptr<Keyboard::KeyboardEventVector> keyboard_events); util::CommandResult<void> NavigateInternal(const GURL& url); @@ -129,6 +137,8 @@ // Bound to the WebDriver thread. base::ThreadChecker thread_checker_; + KeyboardEventInjector keyboard_injector_; + // Anything that interacts with the window must be run on this message loop. scoped_refptr<base::MessageLoopProxy> window_message_loop_;
diff --git a/src/cobalt/webdriver_benchmarks/README.md b/src/cobalt/webdriver_benchmarks/README.md new file mode 100644 index 0000000..da77083 --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/README.md
@@ -0,0 +1,3 @@ +Framework for webdriver-driven benchmarks. + +Please see tests/README.md
diff --git a/src/cobalt/webdriver_benchmarks/__init__.py b/src/cobalt/webdriver_benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/__init__.py
diff --git a/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py b/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py new file mode 100755 index 0000000..fbba36b --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/partial_layout_benchmark.py
@@ -0,0 +1,274 @@ +#!/usr/bin/python2 +"""Webdriver-based benchmarks for partial layout. + +Benchmarks for partial layout changes in Cobalt. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import importlib +import inspect +import os +import re +import sys +import thread +import threading +import unittest + +arg_parser = argparse.ArgumentParser( + description="Runs Webdriver-based Cobalt benchmarks") +arg_parser.add_argument( + "-p", + "--platform", + help="Cobalt platform, eg 'linux-x64x11'." + "Fetched from environment if absent.") +arg_parser.add_argument( + "-e", + "--executable", + help="Path to cobalt executable. " + "Auto-derived if absent.") +arg_parser.add_argument( + "-c", + "--config", + choices=["debug", "devel", "qa", "gold"], + help="Build config (eg, 'qa' or 'devel'). Not used if " + "--executable is specified. Fetched from environment " + "if needed and absent.") +arg_parser.add_argument( + "-d", + "--devkit_name", + help="Devkit or IP address for app_launcher." + "Current hostname used if absent.") +arg_parser.add_argument( + "-o", "--log_file", help="Logfile pathname. stdout if absent.") + +# Pattern to match Cobalt log line for when the WebDriver port has been +# opened. +RE_WEBDRIVER_LISTEN = re.compile(r"Starting WebDriver server on port (\d+)$") + +STARTUP_TIMEOUT_SECONDS = 2 * 60 + +WEBDRIVER_HTTP_TIMEOUT_SECS = 2 * 60 + +COBALT_EXIT_TIMEOUT_SECONDS = 5 + +COBALT_WEBDRIVER_CAPABILITIES = { + "browserName": "cobalt", + "javascriptEnabled": True, + "platform": "LINUX" +} + +_webdriver = None + + +def GetWebDriver(): + """Returns the active connect WebDriver instance.""" + return _webdriver + + +def ImportSeleniumModule(submodule=None): + """Dynamically imports a selenium.webdriver submodule. + + This is done because selenium 3.0 is not commonly pre-installed + on workstations, and we want to have a friendly error message for that + case. + + Args: + submodule: module subpath underneath "selenium.webdriver" + Returns: + appropriate module + """ + if submodule: + module_path = ".".join(("selenium", submodule)) + else: + module_path = "selenium" + # As of this writing, Google uses selenium 3.0.0b2 internally, so + # thats what we will target here as well. + try: + module = importlib.import_module(module_path) + if submodule is None: + # Only the top-level module has __version__ + if not module.__version__.startswith("3.0"): + raise ImportError("Not version 3.0.x") + except ImportError: + sys.stderr.write("Could not import {}\n" + "Please install selenium >= 3.0.0b2.\n" + "Commonly: \"sudo pip install 'selenium>=3.0.0b2'\"\n" + .format(module_path)) + sys.exit(1) + return module + + +class TimeoutException(Exception): + pass + + +class CobaltRunner(object): + """Runs a Cobalt browser w/ a WebDriver client attached.""" + test_script_started = threading.Event() + selenium_webdriver_module = None + webdriver = None + launcher = None + log_file_path = None + thread = None + failed = False + should_exit = threading.Event() + + def __init__(self, platform, executable, devkit_name, log_file_path): + self.selenium_webdriver_module = ImportSeleniumModule("webdriver") + + script_path = os.path.realpath(inspect.getsourcefile(lambda: 0)) + app_launcher_path = os.path.realpath( + os.path.join( + os.path.dirname(script_path), "..", "..", "tools", "lbshell")) + sys.path.append(app_launcher_path) + app_launcher = importlib.import_module("app_launcher") + self.launcher = app_launcher.CreateLauncher( + platform, executable, devkit_name=devkit_name, close_output_file=False) + + self.launcher.SetArgs(["--enable_webdriver"]) + self.launcher.SetOutputCallback(self._HandleLine) + self.log_file_path = log_file_path + self.log_file = None + + def __enter__(self): + self.thread = threading.Thread(target=self.Run) + self.thread.start() + try: + self.WaitForStart() + except KeyboardInterrupt: + # potentially from thread.interrupt_main(). We will treat as + # a timeout regardless + raise TimeoutException + return self + + def __exit__(self, exc_type, exc_value, traceback): + # The unittest module terminates with a SystemExit + # If this is a successful exit, then this is a successful run + success = exc_type is None or (exc_type is SystemExit and + not exc_value.code) + self.SetShouldExit(failed=not success) + self.thread.join(COBALT_EXIT_TIMEOUT_SECONDS) + + def _HandleLine(self, line): + """Internal log line callback.""" + + # Wait for WebDriver port here then connect + if self.test_script_started.is_set(): + return + + match = RE_WEBDRIVER_LISTEN.search(line) + if not match: + return + + port = match.group(1) + print("WebDriver port opened:" + port + "\n", file=self.log_file) + self._StartWebdriver(port) + + def SetShouldExit(self, failed=False): + """Indicates cobalt process should exit.""" + self.failed = failed + self.should_exit.set() + self.launcher.SendKill() + + def _GetIPAddress(self): + return self.launcher.GetIPAddress() + + def _StartWebdriver(self, port): + global _webdriver + url = "http://{}:{}/".format(self._GetIPAddress(), port) + self.webdriver = self.selenium_webdriver_module.Remote( + url, COBALT_WEBDRIVER_CAPABILITIES) + self.webdriver.command_executor.set_timeout(WEBDRIVER_HTTP_TIMEOUT_SECS) + print("Selenium Connected\n", file=self.log_file) + _webdriver = self.webdriver + self.test_script_started.set() + + def WaitForStart(self): + """Waits for the webdriver client to attach to cobalt.""" + if not self.test_script_started.wait(STARTUP_TIMEOUT_SECONDS): + self.SetShouldExit(failed=True) + raise TimeoutException + print("Cobalt started", file=self.log_file) + + def Run(self): + """Thread run routine.""" + + # Use stdout if log_file_path is unspecified + # If log_file_path is specified, make sure to close it + to_close = None + try: + if self.log_file_path: + self.log_file = open(self.log_file_path, "w") + to_close = self.log_file + else: + self.log_file = sys.stdout + + self.launcher.SetOutputFile(self.log_file) + print("Running launcher", file=self.log_file) + self.launcher.Run() + print( + "Cobalt terminated. failed: " + str(self.failed), file=self.log_file) + # This is watched for in webdriver_benchmark_test.py + if not self.failed: + sys.stdout.write("partial_layout_benchmark TEST COMPLETE\n") + # pylint: disable=broad-except + except Exception as ex: + print("Exception running Cobalt " + str(ex), file=sys.stderr) + finally: + if to_close: + to_close.close() + if not self.should_exit.is_set(): + # If the main thread is not expecting us to exit, + # we must interrupt it. + thread.interrupt_main() + return 0 + + +def GetCobaltExecutablePath(platform, config): + """Auto-derives a path to a cobalt executable.""" + if config is None: + try: + config = os.environ["BUILD_TYPE"] + except KeyError: + sys.stderr.write("Must specify --config or --executable\n") + sys.exit(1) + script_path = os.path.realpath(inspect.getsourcefile(lambda: 0)) + script_dir = os.path.dirname(script_path) + out_dir = os.path.join(script_dir, "..", "..", "out") + executable_directory = os.path.join(out_dir, "{}_{}".format(platform, config)) + return os.path.join(executable_directory, "cobalt") + + +def main(): + args = arg_parser.parse_args() + # Keep unittest module from seeing these args + sys.argv = sys.argv[:1] + + platform = args.platform + if platform is None: + try: + platform = os.environ["BUILD_PLATFORM"] + except KeyError: + sys.stderr.write("Must specify --platform\n") + sys.exit(1) + + executable = args.executable + if executable is None: + executable = GetCobaltExecutablePath(platform, args.config) + + try: + with CobaltRunner(platform, executable, args.devkit_name, + args.log_file) as runner: + unittest.main(testRunner=unittest.TextTestRunner( + verbosity=0, stream=runner.log_file)) + except TimeoutException: + print("Timeout waiting for Cobalt to start", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main()
diff --git a/src/cobalt/webdriver_benchmarks/test_tv_testcase.py b/src/cobalt/webdriver_benchmarks/test_tv_testcase.py new file mode 100755 index 0000000..742fa66 --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/test_tv_testcase.py
@@ -0,0 +1,116 @@ +#!/usr/bin/python2 +# Copyright 2016 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. +# ============================================================================== +"""Unit tests for tv_testcase.py.""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys +import unittest + +# This directory is a package +sys.path.insert(0, os.path.abspath('.')) +# pylint: disable=C6203,C6203 +# pylint: disable=g-import-not-at-top +import tv_testcase + + +class MedianPercentileTest(unittest.TestCase): + + def test_empty_case(self): + self.assertEqual(tv_testcase._percentile([], 50), None) + + def test_one_item(self): + self.assertEqual(tv_testcase._percentile([1], 50), 1) + + def test_two_items(self): + self.assertAlmostEqual(tv_testcase._percentile([4, 1], 50), 2.5) + + def test_three_items(self): + self.assertAlmostEqual(tv_testcase._percentile([4, -4, -2], 50), -2) + + def test_four_items(self): + self.assertAlmostEqual(tv_testcase._percentile([4, 0, 1, -2], 50), 0.5) + + +class PercentileTest(unittest.TestCase): + + def test_one_item(self): + self.assertEqual(tv_testcase._percentile([1], 10), 1) + self.assertEqual(tv_testcase._percentile([2], 50), 2) + self.assertEqual(tv_testcase._percentile([3], 90), 3) + + def test_two_items(self): + self.assertEqual(tv_testcase._percentile([2, 1], 10), 1.1) + self.assertEqual(tv_testcase._percentile([2, 3], 50), 2.5) + self.assertEqual(tv_testcase._percentile([3, 4], 90), 3.9) + self.assertEqual(tv_testcase._percentile([3, 4], 100), 4) + + def test_five_items(self): + self.assertEqual(tv_testcase._percentile([2, 1, 3, 4, 5], 10), 1.4) + self.assertEqual(tv_testcase._percentile([2, 1, 3, 4, 5], 50), 3) + self.assertEqual(tv_testcase._percentile([2, 1, 3, 4, 5], 90), 4.6) + self.assertEqual(tv_testcase._percentile([2, 1, 3, 4, 5], 100), 5) + + +class MergeDictTest(unittest.TestCase): + + def test_empty_merge(self): + a = {'x': 4} + b = {} + tv_testcase._merge_dict(a, b) + self.assertEqual(a, {'x': 4}) + + def test_merge_into_empty(self): + a = {} + b = {'x': 4} + tv_testcase._merge_dict(a, b) + self.assertEqual(a, {'x': 4}) + + def test_merge_non_overlapping_item(self): + a = {'x': 4} + b = {'y': 5} + tv_testcase._merge_dict(a, b) + self.assertEqual(a, {'x': 4, 'y': 5}) + + def test_overlapping_item_with_item(self): + a = {'x': 4} + b = {'x': 5} + tv_testcase._merge_dict(a, b) + self.assertEqual(a, {'x': [4, 5]}) + + def test_overlapping_list_with_item(self): + a = {'x': [4]} + b = {'x': 5} + tv_testcase._merge_dict(a, b) + self.assertEqual(a, {'x': [4, 5]}) + + def test_overlapping_list_with_list(self): + a = {'x': [4]} + b = {'x': [5]} + tv_testcase._merge_dict(a, b) + self.assertEqual(a, {'x': [4, 5]}) + + def test_overlapping_item_with_list(self): + a = {'x': 4} + b = {'x': [5]} + tv_testcase._merge_dict(a, b) + self.assertEqual(a, {'x': [4, 5]}) + + +if __name__ == '__main__': + sys.exit(unittest.main())
diff --git a/src/cobalt/webdriver_benchmarks/tests/README.md b/src/cobalt/webdriver_benchmarks/tests/README.md new file mode 100644 index 0000000..a8e30ad --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/tests/README.md
@@ -0,0 +1,29 @@ +Cobalt Webdriver-driven Benchmarks +--------------------- + +This directory contains a set of webdriver-driven benchmarks +for Cobalt. + +Each file should contain a set of tests in Python "unittest" format. + +All tests in all of the files in this directory will be run on the build system. +Results can be recorded in the build results database. + +To run an individual test, simply execute a script directly (or run +all of them via "all.py"). Platform configuration will be inferred from +the environment if set. Otherwise, it must be specified via commandline +parameters. + +To make a new test: + + 1. If appropriate, create a new file borrowing the boilerplate from + an existing simple file, such as "shelf.py" + + 2. If this file contains internal names or details, consider adding it + to the "EXCLUDE.FILES" list. + + 3. Use the `record_result*` methods in the `tv_testcase.TvTestCase` base + class where appropriate. + + 4. Results must be added to the build results database schema. See + the internal "README-Updating-Result-Schema.md" file
diff --git a/src/cobalt/webdriver_benchmarks/tests/__init__.py b/src/cobalt/webdriver_benchmarks/tests/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/tests/__init__.py
diff --git a/src/cobalt/webdriver_benchmarks/tests/all.py b/src/cobalt/webdriver_benchmarks/tests/all.py new file mode 100755 index 0000000..78924c0 --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/tests/all.py
@@ -0,0 +1,38 @@ +#!/usr/bin/python2 +"""Target for running all tests cases.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import importlib +import os +import sys +import unittest + +# The parent directory is a module +sys.path.insert(0, os.path.dirname(os.path.dirname( + os.path.realpath(__file__)))) + +# pylint: disable=C6204,C6203 +import tv_testcase + + +# pylint: disable=unused-argument +def load_tests(loader, tests, pattern): + """This is a Python unittest "load_tests protocol method.""" + s = unittest.TestSuite() + + for f in os.listdir(os.path.dirname(__file__)): + if not f.endswith(".py"): + continue + if f.startswith("_"): + continue + if f == "all.py": + continue + s.addTest(unittest.TestLoader().loadTestsFromModule( + importlib.import_module("tests." + f[:-3]))) + return s + +if __name__ == "__main__": + tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/guide.py b/src/cobalt/webdriver_benchmarks/tests/guide.py new file mode 100755 index 0000000..5c0a011 --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/tests/guide.py
@@ -0,0 +1,45 @@ +#!/usr/bin/python2 +"""Simple guide navigation test.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys + +# The parent directory is a module +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + +# pylint: disable=C6204,C6203 +import tv +import tv_testcase +import partial_layout_benchmark + +# selenium imports +keys = partial_layout_benchmark.ImportSeleniumModule("webdriver.common.keys") + +REPEAT_COUNT = 10 + + +class GuideTest(tv_testcase.TvTestCase): + + def test_simple(self): + layout_times_us = [] + + self.load_tv() + self.assert_displayed(tv.FOCUSED_SHELF) + + for _ in xrange(REPEAT_COUNT): + self.send_keys(tv.FOCUSED_SHELF, keys.Keys.ARROW_LEFT) + self.assert_displayed(tv.FOCUSED_GUIDE) + self.wait_for_layout_complete() + self.send_keys(tv.FOCUSED_GUIDE, keys.Keys.ARROW_RIGHT) + self.wait_for_layout_complete_after_focused_shelf() + layout_times_us.append(self.get_keyup_layout_duration_us()) + + self.record_result_percentile("guideTestLayout95thUs", layout_times_us, 95) + + +if __name__ == "__main__": + tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/shelf.py b/src/cobalt/webdriver_benchmarks/tests/shelf.py new file mode 100755 index 0000000..c73255b --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/tests/shelf.py
@@ -0,0 +1,48 @@ +#!/usr/bin/python2 +"""Simple shelf navigation test.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys + +# The parent directory is a module +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + +# pylint: disable=C6204,C6203 +import tv +import tv_testcase +import partial_layout_benchmark + +# selenium imports +keys = partial_layout_benchmark.ImportSeleniumModule("webdriver.common.keys") + +DEFAULT_SHELVES_COUNT = 10 +SHELF_ITEMS_COUNT = 10 + + +class ShelfTest(tv_testcase.TvTestCase): + + def test_simple(self): + layout_times_us = [] + self.load_tv() + self.assert_displayed(tv.FOCUSED_SHELF) + + for _ in xrange(DEFAULT_SHELVES_COUNT): + self.send_keys(tv.FOCUSED_SHELF, keys.Keys.ARROW_DOWN) + self.wait_for_layout_complete_after_focused_shelf() + layout_times_us.append(self.get_keyup_layout_duration_us()) + + for _ in xrange(SHELF_ITEMS_COUNT): + self.send_keys(tv.FOCUSED_TILE, keys.Keys.ARROW_RIGHT) + self.wait_for_layout_complete_after_focused_shelf() + layout_times_us.append(self.get_keyup_layout_duration_us()) + + self.record_result_percentile("webdriverBenchmarkShelfLayout95thUsec", + layout_times_us, 95) + + +if __name__ == "__main__": + tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/tests/time_to_shelf.py b/src/cobalt/webdriver_benchmarks/tests/time_to_shelf.py new file mode 100755 index 0000000..e180362 --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/tests/time_to_shelf.py
@@ -0,0 +1,61 @@ +#!/usr/bin/python2 +# Copyright 2016 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. +# ============================================================================== +"""Calculate time to download YouTube TV app and initial layout of shelves.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import sys + +# The parent directory is a module +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + +# pylint: disable=C6204,C6203 +import tv_testcase + + +class TimeToShelf(tv_testcase.TvTestCase): + + def test_simple(self): + """This test tries to measure the startup time for the YouTube TV page. + + Specifically, this test uses the Cobalt CVal Cobalt.Lifetime, which gets + updated ~60Hz on a best effort basis. This value is in microseconds. + + Note: t0 is defined after Cobalt starts up, but has not navigated to a page. + If that true startup time metric is desired, perhaps a separate should be + used. + """ + metrics_array = [] + blank_startup_time_microseconds = self.get_int_cval('Cobalt.Lifetime') + for _ in range(10): + t0 = self.get_int_cval('Cobalt.Lifetime') + self.load_tv() + self.wait_for_layout_complete_after_focused_shelf() + t1 = self.get_int_cval('Cobalt.Lifetime') + startup_time_microseconds = t1 - t0 + metrics_array.append(startup_time_microseconds) + + self.record_result_median('timeToShelfTestTimeShelfDisplayMedianUs', + metrics_array) + self.record_result('timeToShelfBlankStartupTimeUs', + blank_startup_time_microseconds) + + +if __name__ == '__main__': + tv_testcase.main()
diff --git a/src/cobalt/webdriver_benchmarks/timer.py b/src/cobalt/webdriver_benchmarks/timer.py new file mode 100644 index 0000000..0ee08dc --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/timer.py
@@ -0,0 +1,94 @@ +# Copyright 2016 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. +# ============================================================================== +"""Contains a contextmanager for a timer. + + Example usage: + + from timer import Timer + + with Timer('SomeTask') as t: + # Do some time consuming task + print('So far {} seconds have passed'.format(t.seconds_elapsed)) + + print(t) # This will print something like 'SomeTask took 1.2 seconds' +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import timeit + + +class Timer(object): + """ContextManager for measuring time since an event.""" + + def __init__(self, description): + """Initializes the timer. + + Args: + description: A string containing the description of the timer. + """ + self.description = description + self._timer = timeit.default_timer # Choose best timer for the platform. + self.start_time = None + self._seconds_elapsed = None + + def __enter__(self): + """Starts the timer. + + __enter__ allows this class to be used as a ContextManager. + + Returns: + The object itself so it works with the |with| statement. + """ + self.start_time = self._timer() + return self + + def __exit__(self, unused_ex_type, unused_ex, unused_ex_trace): + """Stops the timer and records the duration. + + __enter__ allows this class to be used as a ContextManager. + + Returns: + False. Any exception raised within the body of the contextmanager will + propogate up the stack. + """ + self._seconds_elapsed = self._timer() - self.start_time + return False + + @property + def seconds_elapsed(self): + """Number of seconds elapsed since start until now or time when timer ended. + + This property will return the number of seconds since the timer was started, + or the duration of the timer's context manager. + + Returns: + A float containing the number of seconds elapsed. + """ + if self._seconds_elapsed is None: + return self._timer() - self.start_time + + return self._seconds_elapsed + + def __str__(self): + """A magic method for generating a string representation of the object. + + Returns: + A string containing a description and a human readable version of timer's + value. + """ + return '{} took {} seconds.'.format(self.description, self.seconds_elapsed)
diff --git a/src/cobalt/webdriver_benchmarks/tv.py b/src/cobalt/webdriver_benchmarks/tv.py new file mode 100644 index 0000000..f9c0411 --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/tv.py
@@ -0,0 +1,10 @@ +"""CSS Constants.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +FOCUSED_GUIDE = '.focused.guide' +FOCUSED_SHELF = '.focused.selected.shelf' +FOCUSED_SHELF_TITLE = '.focused.selected.shelf .shelf-header-title .main' +FOCUSED_TILE = '.focused.tile'
diff --git a/src/cobalt/webdriver_benchmarks/tv_testcase.py b/src/cobalt/webdriver_benchmarks/tv_testcase.py new file mode 100644 index 0000000..c9a16ca --- /dev/null +++ b/src/cobalt/webdriver_benchmarks/tv_testcase.py
@@ -0,0 +1,353 @@ +"""Base class for WebDriver tests.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import json +import logging +import math +import os +import sys +import time +import unittest + +# pylint: disable=C6204 +try: + # This code works for Python 2. + import urlparse + from urllib import urlencode +except ImportError: + # This code works for Python 3. + import urllib.parse as urlparse + from urllib.parse import urlencode + +# This directory is a package +sys.path.insert(0, os.path.abspath(".")) +# pylint: disable=C6204,C6203 +import partial_layout_benchmark +import tv + +# selenium imports +# pylint: disable=C0103 +WebDriverWait = partial_layout_benchmark.ImportSeleniumModule( + submodule="webdriver.support.ui").WebDriverWait + +ElementNotVisibleException = (partial_layout_benchmark.ImportSeleniumModule( + submodule="common.exceptions").ElementNotVisibleException) + +BASE_URL = "https://www.youtube.com/" +TV_APP_PATH = "/tv" +BASE_PARAMS = {"env_forcedOffAllExperiments": True} +PAGE_LOAD_WAIT_SECONDS = 30 +LAYOUT_TIMEOUT_SECONDS = 5 +# Today, Cobalt's WebDriver has a race that +# can leave the former page's DOM visible after a navigate. +# As a workaround, sleep for a bit +# b/33275371 +COBALT_POST_NAVIGATE_SLEEP_SECONDS = 5 + + +def _percentile(results, percentile): + """Returns the percentile of an array. + + This method interpolates between two numbers if the percentile lands between + two data points. + + Args: + results: Sortable results array. + percentile: A number ranging from 0-100. + Returns: + Appropriate value. + Raises: + RuntimeError: Raised on invalid args. + """ + if not results: + return None + if percentile > 100 or percentile < 0: + raise RuntimeError("percentile must be 0-100") + sorted_results = sorted(results) + + if percentile == 100: + return sorted_results[-1] + fractional, index = math.modf((len(sorted_results) - 1) * (percentile * 0.01)) + index = int(index) + + if len(sorted_results) == index + 1: + return sorted_results[index] + + return sorted_results[index] * (1 - fractional + ) + sorted_results[index + 1] * fractional + + +def _merge_dict(merge_into, merge_from): + """Merges the second dict into the first dict. + + Merge into differs from update in that it will not override values. If the + values already exist, the resulting value will be a list with a union of + existing and new items. + + Args: + merge_into: An output dict to merge values into. + merge_from: An input dict to iterate over and insert values from. + + Returns: + None + """ + if not merge_from: + return + for k, v in merge_from.items(): + try: + existing_value = merge_into[k] + except KeyError: + merge_into[k] = v + continue + + if not isinstance(v, list): + v = [v] + if isinstance(existing_value, list): + existing_value.extend(v) + else: + new_value = [existing_value] + new_value.extend(v) + merge_into[k] = new_value + + +class TvTestCase(unittest.TestCase): + """Base class for WebDriver tests. + + Style note: snake_case is used for function names here so as to match + with an internal class with the same name. + """ + + class LayoutTimeoutException(BaseException): + """Exception thrown when layout did not complete in time.""" + + @classmethod + def setUpClass(cls): + print("Running " + cls.__name__) + + @classmethod + def tearDownClass(cls): + print("Done " + cls.__name__) + + def get_webdriver(self): + return partial_layout_benchmark.GetWebDriver() + + def get_cval(self, cval_name): + """Returns value of a cval. + + Args: + cval_name: Name of the cval. + Returns: + Value of the cval. + """ + javascript_code = "return h5vcc.cVal.getValue('{}')".format(cval_name) + return self.get_webdriver().execute_script(javascript_code) + + def get_int_cval(self, cval_name): + """Returns int value of a cval. + + The cval value must be an integer, if it is a float, then this function will + throw a ValueError. + + Args: + cval_name: Name of the cval. + Returns: + Value of the cval. + Raises: + ValueError if the cval is cannot be converted to int. + """ + answer = self.get_cval(cval_name) + if answer is None: + return answer + return int(answer) + + def goto(self, path, query_params=None): + """Goes to a path off of BASE_URL. + + Args: + path: URL path without the hostname. + query_params: Dictionary of parameter names and values. + Raises: + Underlying WebDriver exceptions + """ + parsed_url = list(urlparse.urlparse(BASE_URL)) + parsed_url[2] = path + query_dict = BASE_PARAMS.copy() + if query_params: + query_dict.update(urlparse.parse_qsl(parsed_url[4])) + _merge_dict(query_dict, query_params) + parsed_url[4] = urlencode(query_dict, doseq=True) + final_url = urlparse.urlunparse(parsed_url) + self.get_webdriver().get(final_url) + time.sleep(COBALT_POST_NAVIGATE_SLEEP_SECONDS) + + def load_tv(self, label=None, additional_query_params=None): + """Loads the main TV page and waits for it to display. + + Args: + label: A value for the label query parameter. + additional_query_params: A dict containing additional query parameters. + Raises: + Underlying WebDriver exceptions + """ + query_params = {} + if label is not None: + query_params = {"label": label} + if additional_query_params is not None: + query_params.update(additional_query_params) + self.goto(TV_APP_PATH, query_params) + # Note that the internal tests use "expect_transition" which is + # a mechanism that sets a maximum timeout for a "@with_retries" + # decorator-driven success retry loop for subsequent webdriver requests. + # + # We'll skip that sophistication here. + self.poll_until_found(tv.FOCUSED_SHELF) + + def poll_until_found(self, css_selector): + """Polls until an element is found. + + Args: + css_selector: A CSS selector + Raises: + Underlying WebDriver exceptions + """ + start_time = time.time() + while ((not self.find_elements(css_selector)) and + (time.time() - start_time < PAGE_LOAD_WAIT_SECONDS)): + time.sleep(1) + self.assert_displayed(css_selector) + + def unique_find(self, unique_selector): + """Finds and returns a uniquely selected element. + + Args: + unique_selector: A CSS selector that will select only one element + Raises: + Underlying WebDriver exceptions + AssertError: the element isn't unique + Returns: + Element + """ + return self.find_elements(unique_selector, expected_num=1)[0] + + def assert_displayed(self, css_selector): + """Asserts that an element is displayed. + + Args: + css_selector: A CSS selector + Raises: + Underlying WebDriver exceptions + AssertError: the element isn't found + """ + # TODO does not actually assert that it's visible, like webdriver.py + # probably does. + self.assertTrue(self.unique_find(css_selector)) + + def find_elements(self, css_selector, expected_num=None): + """Finds elements based on a selector. + + Args: + css_selector: A CSS selector + expected_num: Expected number of matching elements + Raises: + Underlying WebDriver exceptions + AssertError: expected_num isn't met + Returns: + Array of selected elements + """ + elements = self.get_webdriver().find_elements_by_css_selector(css_selector) + if expected_num is not None: + self.assertEqual(len(elements), expected_num) + return elements + + def send_keys(self, css_selector, keys): + """Sends keys to an element uniquely identified by a selector. + + This method retries for a timeout period if the selected element + could not be immediately found. If the retries do not succeed, + the underlying exception is passed through. + + Args: + css_selector: A CSS selector + keys: key events + + Raises: + Underlying WebDriver exceptions + """ + start_time = time.time() + while True: + try: + element = self.unique_find(css_selector) + element.send_keys(keys) + return + except ElementNotVisibleException: + # TODO ElementNotVisibleException seems to be considered + # a "falsy" exception in the internal tests, which seems to mean + # it would not be retried. But here, it seems essential. + if time.time() - start_time >= PAGE_LOAD_WAIT_SECONDS: + raise + time.sleep(1) + + def wait_for_layout_complete(self): + """Waits for Cobalt to complete pending layouts.""" + start_time = time.time() + while self.get_int_cval("Event.MainWebModule.IsProcessing"): + + if time.time() - start_time > LAYOUT_TIMEOUT_SECONDS: + raise TvTestCase.LayoutTimeoutException() + + time.sleep(0.1) + + def get_keyup_layout_duration_us(self): + return self.get_int_cval("Event.Duration.MainWebModule.KeyUp") + + def wait_for_layout_complete_after_focused_shelf(self): + """Waits for Cobalt to focus on a shelf and complete pending layouts.""" + self.poll_until_found(tv.FOCUSED_SHELF) + self.assert_displayed(tv.FOCUSED_SHELF_TITLE) + self.wait_for_layout_complete() + + def record_result(self, name, result): + """Records an individual scalar result of a benchmark. + + Args: + name: name of test case + result: Test result. Must be JSON encodable scalar. + """ + value_to_record = result + + string_value_to_record = json.JSONEncoder().encode(value_to_record) + print("tv_testcase RESULT: {} {}".format(name, string_value_to_record)) + + def record_result_median(self, name, results): + """Records the median of an array of results. + + Args: + name: name of test case + results: Test results array. Must be array of JSON encodable scalar. + """ + value_to_record = _percentile(results, 50) + + string_value_to_record = json.JSONEncoder().encode(value_to_record) + print("tv_testcase RESULT: {} {}".format(name, string_value_to_record)) + + def record_result_percentile(self, name, results, percentile): + """Records the percentile of an array of results. + + Args: + name: The (string) name of test case. + results: Test results array. Must be array of JSON encodable scalars. + percentile: A number ranging from 0-100. + Raises: + RuntimeError: Raised on invalid args. + """ + value_to_record = _percentile(results, percentile) + string_value_to_record = json.JSONEncoder().encode(value_to_record) + print("tv_testcase RESULT: {} {}".format(name, string_value_to_record)) + + +def main(): + logging.basicConfig(level=logging.DEBUG) + partial_layout_benchmark.main()
diff --git a/src/cobalt/xhr/xml_http_request.cc b/src/cobalt/xhr/xml_http_request.cc index d1877a9..e514ed2 100644 --- a/src/cobalt/xhr/xml_http_request.cc +++ b/src/cobalt/xhr/xml_http_request.cc
@@ -21,6 +21,7 @@ #include "base/compiler_specific.h" #include "base/string_number_conversions.h" #include "base/string_util.h" +#include "base/time.h" #include "cobalt/base/polymorphic_downcast.h" #include "cobalt/base/source_location.h" #include "cobalt/base/tokens.h" @@ -361,13 +362,13 @@ StartRequest(request_body_text); // Start the timeout timer running, if applicable. - send_start_time_ = base::Time::Now(); + send_start_time_ = base::TimeTicks::Now(); if (timeout_ms_) { StartTimer(base::TimeDelta()); } // Timer for throttling progress events. - upload_last_progress_time_ = base::Time(); - last_progress_time_ = base::Time(); + upload_last_progress_time_ = base::TimeTicks(); + last_progress_time_ = base::TimeTicks(); } base::optional<std::string> XMLHttpRequest::GetResponseHeader( @@ -531,7 +532,7 @@ } else if (sent_) { // Timeout was set while request was in flight. Timeout is relative to // the start of the request. - StartTimer(base::Time::Now() - send_start_time_); + StartTimer(base::TimeTicks::Now() - send_start_time_); } } @@ -614,7 +615,7 @@ ChangeState(kLoading); // Send a progress notification if at least 50ms have elapsed. - const base::Time now = base::Time::Now(); + const base::TimeTicks now = base::TimeTicks::Now(); const base::TimeDelta elapsed(now - last_progress_time_); if (elapsed > base::TimeDelta::FromMilliseconds(kProgressPeriodMs)) { last_progress_time_ = now; @@ -657,7 +658,7 @@ // Fire a progress event if either the upload just completed, or if enough // time has elapsed since we sent the last one. - const base::Time now = base::Time::Now(); + const base::TimeTicks now = base::TimeTicks::Now(); const base::TimeDelta elapsed(now - upload_last_progress_time_); if (upload_complete_ || (elapsed > base::TimeDelta::FromMilliseconds(kProgressPeriodMs))) { @@ -702,6 +703,7 @@ << ") " << *this << std::endl << script::StackTraceToString( settings_->global_environment()->GetStackTrace()); + stop_timeout_ = true; // Step 1 TerminateRequest(); // Steps 2-4 @@ -735,9 +737,13 @@ // Subtract any time that has already elapsed from the timeout. // This is in case the user has set a timeout after send() was already in // flight. - timer_.Start(FROM_HERE, - base::TimeDelta::FromMilliseconds(timeout_ms_) - time_since_send, - this, &XMLHttpRequest::OnTimeout); + base::TimeDelta delay = std::max( + base::TimeDelta(), + base::TimeDelta::FromMilliseconds(timeout_ms_) - time_since_send); + + // Queue the callback even if delay ends up being zero, to preserve the + // previous semantics. + timer_.Start(FROM_HERE, delay, this, &XMLHttpRequest::OnTimeout); } void XMLHttpRequest::ChangeState(XMLHttpRequest::State new_state) {
diff --git a/src/cobalt/xhr/xml_http_request.h b/src/cobalt/xhr/xml_http_request.h index d3f5aa6..83d2d7f 100644 --- a/src/cobalt/xhr/xml_http_request.h +++ b/src/cobalt/xhr/xml_http_request.h
@@ -241,11 +241,11 @@ // For handling send() timeout. base::OneShotTimer<XMLHttpRequest> timer_; - base::Time send_start_time_; + base::TimeTicks send_start_time_; // Time to throttle progress notifications. - base::Time last_progress_time_; - base::Time upload_last_progress_time_; + base::TimeTicks last_progress_time_; + base::TimeTicks upload_last_progress_time_; // All members requiring initialization are grouped below. dom::DOMSettings* settings_;
diff --git a/src/cobalt/xhr/xml_http_request_event_target.cc b/src/cobalt/xhr/xml_http_request_event_target.cc index ef9eedb..20b9eeb 100644 --- a/src/cobalt/xhr/xml_http_request_event_target.cc +++ b/src/cobalt/xhr/xml_http_request_event_target.cc
@@ -64,37 +64,65 @@ void XMLHttpRequestEventTarget::set_onabort( const EventListenerScriptObject& listener) { - onabort_listener_.emplace(this, listener); + if (listener.IsNull()) { + onabort_listener_ = base::nullopt; + } else { + onabort_listener_.emplace(this, listener); + } SetAttributeEventListener(base::Tokens::abort(), listener); } void XMLHttpRequestEventTarget::set_onerror( const EventListenerScriptObject& listener) { - onerror_listener_.emplace(this, listener); + if (listener.IsNull()) { + onerror_listener_ = base::nullopt; + } else { + onerror_listener_.emplace(this, listener); + } SetAttributeEventListener(base::Tokens::error(), listener); } void XMLHttpRequestEventTarget::set_onload( const EventListenerScriptObject& listener) { - onload_listener_.emplace(this, listener); + if (listener.IsNull()) { + onload_listener_ = base::nullopt; + } else { + onload_listener_.emplace(this, listener); + } SetAttributeEventListener(base::Tokens::load(), listener); } void XMLHttpRequestEventTarget::set_onloadend( const EventListenerScriptObject& listener) { - onloadend_listener_.emplace(this, listener); + if (listener.IsNull()) { + onloadend_listener_ = base::nullopt; + } else { + onloadend_listener_.emplace(this, listener); + } SetAttributeEventListener(base::Tokens::loadend(), listener); } void XMLHttpRequestEventTarget::set_onloadstart( const EventListenerScriptObject& listener) { - onloadstart_listener_.emplace(this, listener); + if (listener.IsNull()) { + onloadstart_listener_ = base::nullopt; + } else { + onloadstart_listener_.emplace(this, listener); + } SetAttributeEventListener(base::Tokens::loadstart(), listener); } void XMLHttpRequestEventTarget::set_onprogress( const EventListenerScriptObject& listener) { - onprogress_listener_.emplace(this, listener); + if (listener.IsNull()) { + onprogress_listener_ = base::nullopt; + } else { + onprogress_listener_.emplace(this, listener); + } SetAttributeEventListener(base::Tokens::progress(), listener); } void XMLHttpRequestEventTarget::set_ontimeout( const EventListenerScriptObject& listener) { - ontimeout_listener_.emplace(this, listener); + if (listener.IsNull()) { + ontimeout_listener_ = base::nullopt; + } else { + ontimeout_listener_.emplace(this, listener); + } SetAttributeEventListener(base::Tokens::timeout(), listener); } } // namespace xhr
diff --git a/src/cobalt/xhr/xml_http_request_test.cc b/src/cobalt/xhr/xml_http_request_test.cc index ac3b5c1..ae2f187 100644 --- a/src/cobalt/xhr/xml_http_request_test.cc +++ b/src/cobalt/xhr/xml_http_request_test.cc
@@ -92,7 +92,8 @@ class FakeSettings : public dom::DOMSettings { public: - FakeSettings() : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL) {} + FakeSettings() + : dom::DOMSettings(0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL) {} GURL base_url() const OVERRIDE { return GURL("http://example.com"); } };
diff --git a/src/content/browser/speech/chunked_byte_buffer.cc b/src/content/browser/speech/chunked_byte_buffer.cc new file mode 100644 index 0000000..8ac9c92 --- /dev/null +++ b/src/content/browser/speech/chunked_byte_buffer.cc
@@ -0,0 +1,134 @@ +// 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 "content/browser/speech/chunked_byte_buffer.h" + +#include <algorithm> +#include <utility> + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace { + +static const size_t kHeaderLength = sizeof(uint32_t); + +COMPILE_ASSERT(sizeof(size_t) >= kHeaderLength, + chunked_byte_buffer_not_supported_on_this_architecture); + +uint32_t ReadBigEndian32(const uint8_t* buffer) { + return (static_cast<uint32_t>(buffer[3])) | + (static_cast<uint32_t>(buffer[2]) << 8) | + (static_cast<uint32_t>(buffer[1]) << 16) | + (static_cast<uint32_t>(buffer[0]) << 24); +} + +} // namespace + +namespace content { + +ChunkedByteBuffer::ChunkedByteBuffer() + : partial_chunk_(new Chunk()), + total_bytes_stored_(0) { +} + +ChunkedByteBuffer::~ChunkedByteBuffer() { + Clear(); +} + +void ChunkedByteBuffer::Append(const uint8_t* start, size_t length) { + size_t remaining_bytes = length; + const uint8_t* next_data = start; + + while (remaining_bytes > 0) { + DCHECK(partial_chunk_ != NULL); + size_t insert_length = 0; + bool header_completed = false; + bool content_completed = false; + std::vector<uint8_t>* insert_target; + + if (partial_chunk_->header.size() < kHeaderLength) { + const size_t bytes_to_complete_header = + kHeaderLength - partial_chunk_->header.size(); + insert_length = std::min(bytes_to_complete_header, remaining_bytes); + insert_target = &partial_chunk_->header; + header_completed = (remaining_bytes >= bytes_to_complete_header); + } else { + DCHECK_LT(partial_chunk_->content->size(), + partial_chunk_->ExpectedContentLength()); + const size_t bytes_to_complete_chunk = + partial_chunk_->ExpectedContentLength() - + partial_chunk_->content->size(); + insert_length = std::min(bytes_to_complete_chunk, remaining_bytes); + insert_target = partial_chunk_->content.get(); + content_completed = (remaining_bytes >= bytes_to_complete_chunk); + } + + DCHECK_GT(insert_length, 0U); + DCHECK_LE(insert_length, remaining_bytes); + DCHECK_LE(next_data + insert_length, start + length); + insert_target->insert(insert_target->end(), + next_data, + next_data + insert_length); + next_data += insert_length; + remaining_bytes -= insert_length; + + if (header_completed) { + DCHECK_EQ(partial_chunk_->header.size(), kHeaderLength); + if (partial_chunk_->ExpectedContentLength() == 0) { + // Handle zero-byte chunks. + chunks_.push_back(partial_chunk_.release()); + partial_chunk_.reset(new Chunk()); + } else { + partial_chunk_->content->reserve( + partial_chunk_->ExpectedContentLength()); + } + } else if (content_completed) { + DCHECK_EQ(partial_chunk_->content->size(), + partial_chunk_->ExpectedContentLength()); + chunks_.push_back(partial_chunk_.release()); + partial_chunk_.reset(new Chunk()); + } + } + DCHECK_EQ(next_data, start + length); + total_bytes_stored_ += length; +} + +void ChunkedByteBuffer::Append(const std::string& string) { + Append(reinterpret_cast<const uint8_t*>(string.data()), string.size()); +} + +bool ChunkedByteBuffer::HasChunks() const { + return !chunks_.empty(); +} + +scoped_ptr<std::vector<uint8_t> > ChunkedByteBuffer::PopChunk() { + if (chunks_.empty()) + return scoped_ptr<std::vector<uint8_t> >().Pass(); + scoped_ptr<Chunk> chunk(*chunks_.begin()); + chunks_.weak_erase(chunks_.begin()); + DCHECK_EQ(chunk->header.size(), kHeaderLength); + DCHECK_EQ(chunk->content->size(), chunk->ExpectedContentLength()); + total_bytes_stored_ -= chunk->content->size(); + total_bytes_stored_ -= kHeaderLength; + return chunk->content.Pass(); +} + +void ChunkedByteBuffer::Clear() { + chunks_.clear(); + partial_chunk_.reset(new Chunk()); + total_bytes_stored_ = 0; +} + +ChunkedByteBuffer::Chunk::Chunk() : content(new std::vector<uint8_t>()) {} + +ChunkedByteBuffer::Chunk::~Chunk() { +} + +size_t ChunkedByteBuffer::Chunk::ExpectedContentLength() const { + DCHECK_EQ(header.size(), kHeaderLength); + return static_cast<size_t>(ReadBigEndian32(&header[0])); +} + +} // namespace content
diff --git a/src/content/browser/speech/chunked_byte_buffer.h b/src/content/browser/speech/chunked_byte_buffer.h new file mode 100644 index 0000000..483ddb9 --- /dev/null +++ b/src/content/browser/speech/chunked_byte_buffer.h
@@ -0,0 +1,74 @@ +// 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. + +#ifndef CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_ +#define CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_ + +#include <memory> +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" + +namespace content { + +// Models a chunk-oriented byte buffer. The term chunk is herein defined as an +// arbitrary sequence of bytes that is preceeded by N header bytes, indicating +// its size. Data may be appended to the buffer with no particular respect of +// chunks boundaries. However, chunks can be extracted (FIFO) only when their +// content (according to their header) is fully available in the buffer. +// The current implementation support only 4 byte Big Endian headers. +// Empty chunks (i.e. the sequence 00 00 00 00) are NOT allowed. +// +// E.g. 00 00 00 04 xx xx xx xx 00 00 00 02 yy yy 00 00 00 04 zz zz zz zz +// [----- CHUNK 1 -------] [--- CHUNK 2 ---] [------ CHUNK 3 ------] +class ChunkedByteBuffer { + public: + ChunkedByteBuffer(); + ~ChunkedByteBuffer(); + + // Appends |length| bytes starting from |start| to the buffer. + void Append(const uint8_t* start, size_t length); + + // Appends bytes contained in the |string| to the buffer. + void Append(const std::string& string); + + // Checks whether one or more complete chunks are available in the buffer. + bool HasChunks() const; + + // If enough data is available, reads and removes the first complete chunk + // from the buffer. Returns a NULL pointer if no complete chunk is available. + scoped_ptr<std::vector<uint8_t> > PopChunk(); + + // Clears all the content of the buffer. + void Clear(); + + // Returns the number of raw bytes (including headers) present. + size_t GetTotalLength() const { return total_bytes_stored_; } + + private: + struct Chunk { + Chunk(); + ~Chunk(); + + std::vector<uint8_t> header; + scoped_ptr<std::vector<uint8_t> > content; + size_t ExpectedContentLength() const; + + private: + DISALLOW_COPY_AND_ASSIGN(Chunk); + }; + + ScopedVector<Chunk> chunks_; + scoped_ptr<Chunk> partial_chunk_; + size_t total_bytes_stored_; + + DISALLOW_COPY_AND_ASSIGN(ChunkedByteBuffer); +}; + + +} // namespace content + +#endif // CONTENT_BROWSER_SPEECH_CHUNKED_BYTE_BUFFER_H_
diff --git a/src/content/browser/speech/chunked_byte_buffer_unittest.cc b/src/content/browser/speech/chunked_byte_buffer_unittest.cc new file mode 100644 index 0000000..9b7be0f --- /dev/null +++ b/src/content/browser/speech/chunked_byte_buffer_unittest.cc
@@ -0,0 +1,74 @@ +// 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 <string> +#include <vector> + +#include "content/browser/speech/chunked_byte_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace content { + +typedef std::vector<uint8_t> ByteVector; + +TEST(ChunkedByteBufferTest, BasicTest) { + ChunkedByteBuffer buffer; + + const uint8_t kChunks[] = { + 0x00, 0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04, // Chunk 1: 4 bytes + 0x00, 0x00, 0x00, 0x02, 0x05, 0x06, // Chunk 2: 2 bytes + 0x00, 0x00, 0x00, 0x01, 0x07 // Chunk 3: 1 bytes + }; + + EXPECT_EQ(0U, buffer.GetTotalLength()); + EXPECT_FALSE(buffer.HasChunks()); + + // Append partially chunk 1. + buffer.Append(kChunks, 2); + EXPECT_EQ(2U, buffer.GetTotalLength()); + EXPECT_FALSE(buffer.HasChunks()); + + // Complete chunk 1. + buffer.Append(kChunks + 2, 6); + EXPECT_EQ(8U, buffer.GetTotalLength()); + EXPECT_TRUE(buffer.HasChunks()); + + // Append fully chunk 2. + buffer.Append(kChunks + 8, 6); + EXPECT_EQ(14U, buffer.GetTotalLength()); + EXPECT_TRUE(buffer.HasChunks()); + + // Remove and check chunk 1. + scoped_ptr<ByteVector> chunk; + chunk = buffer.PopChunk().Pass(); + EXPECT_TRUE(chunk != NULL); + EXPECT_EQ(4U, chunk->size()); + EXPECT_EQ(0, std::char_traits<uint8_t>::compare(kChunks + 4, &(*chunk)[0], + chunk->size())); + EXPECT_EQ(6U, buffer.GetTotalLength()); + EXPECT_TRUE(buffer.HasChunks()); + + // Read and check chunk 2. + chunk = buffer.PopChunk().Pass(); + EXPECT_TRUE(chunk != NULL); + EXPECT_EQ(2U, chunk->size()); + EXPECT_EQ(0, std::char_traits<uint8_t>::compare(kChunks + 12, &(*chunk)[0], + chunk->size())); + EXPECT_EQ(0U, buffer.GetTotalLength()); + EXPECT_FALSE(buffer.HasChunks()); + + // Append fully chunk 3. + buffer.Append(kChunks + 14, 5); + EXPECT_EQ(5U, buffer.GetTotalLength()); + + // Remove and check chunk 3. + chunk = buffer.PopChunk().Pass(); + EXPECT_TRUE(chunk != NULL); + EXPECT_EQ(1U, chunk->size()); + EXPECT_EQ((*chunk)[0], kChunks[18]); + EXPECT_EQ(0U, buffer.GetTotalLength()); + EXPECT_FALSE(buffer.HasChunks()); +} + +} // namespace content
diff --git a/src/content/browser/speech/endpointer/endpointer.cc b/src/content/browser/speech/endpointer/endpointer.cc new file mode 100644 index 0000000..e6d9941 --- /dev/null +++ b/src/content/browser/speech/endpointer/endpointer.cc
@@ -0,0 +1,187 @@ +// 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 "content/browser/speech/endpointer/endpointer.h" + +#include "base/time.h" + +using base::Time; + +namespace { +// Only send |kFrameSize| of audio data to energy endpointer each time. +// It should be smaller than the samples of audio bus which is passed in +// |ProcessAudio|. +const int kFrameSize = 160; +} + +namespace content { + +Endpointer::Endpointer(int sample_rate) + : speech_input_possibly_complete_silence_length_us_(-1), + speech_input_complete_silence_length_us_(-1), + audio_frame_time_us_(0), + sample_rate_(sample_rate), + frame_rate_(sample_rate / kFrameSize) { + Reset(); + + speech_input_minimum_length_us_ = + static_cast<int64_t>(1.7 * Time::kMicrosecondsPerSecond); + speech_input_complete_silence_length_us_ = + static_cast<int64_t>(0.5 * Time::kMicrosecondsPerSecond); + long_speech_input_complete_silence_length_us_ = -1; + long_speech_length_us_ = -1; + speech_input_possibly_complete_silence_length_us_ = + 1 * Time::kMicrosecondsPerSecond; + + // Set the default configuration for Push To Talk mode. + EnergyEndpointerParams ep_config; + ep_config.set_frame_period(1.0f / static_cast<float>(frame_rate_)); + ep_config.set_frame_duration(1.0f / static_cast<float>(frame_rate_)); + ep_config.set_endpoint_margin(0.2f); + ep_config.set_onset_window(0.15f); + ep_config.set_speech_on_window(0.4f); + ep_config.set_offset_window(0.15f); + ep_config.set_onset_detect_dur(0.09f); + ep_config.set_onset_confirm_dur(0.075f); + ep_config.set_on_maintain_dur(0.10f); + ep_config.set_offset_confirm_dur(0.12f); + ep_config.set_decision_threshold(1000.0f); + ep_config.set_min_decision_threshold(50.0f); + ep_config.set_fast_update_dur(0.2f); + ep_config.set_sample_rate(static_cast<float>(sample_rate)); + ep_config.set_min_fundamental_frequency(57.143f); + ep_config.set_max_fundamental_frequency(400.0f); + ep_config.set_contamination_rejection_period(0.25f); + energy_endpointer_.Init(ep_config); +} + +void Endpointer::Reset() { + old_ep_status_ = EP_PRE_SPEECH; + waiting_for_speech_possibly_complete_timeout_ = false; + waiting_for_speech_complete_timeout_ = false; + speech_previously_detected_ = false; + speech_input_complete_ = false; + audio_frame_time_us_ = 0; // Reset time for packets sent to endpointer. + speech_end_time_us_ = -1; + speech_start_time_us_ = -1; +} + +void Endpointer::StartSession() { + Reset(); + energy_endpointer_.StartSession(); +} + +void Endpointer::EndSession() { + energy_endpointer_.EndSession(); +} + +void Endpointer::SetEnvironmentEstimationMode() { + Reset(); + energy_endpointer_.SetEnvironmentEstimationMode(); +} + +void Endpointer::SetUserInputMode() { + energy_endpointer_.SetUserInputMode(); +} + +EpStatus Endpointer::Status(int64_t* time) { + return energy_endpointer_.Status(time); +} + +EpStatus Endpointer::ProcessAudio( + const ShellAudioBus& audio_bus, float* rms_out) { + DCHECK_EQ(audio_bus.channels(), 1); + + const size_t num_samples = audio_bus.frames(); + const int16_t* audio_data = NULL; + + ShellAudioBus int16_audio_bus(1, num_samples, ShellAudioBus::kInt16, + ShellAudioBus::kInterleaved); + + if (audio_bus.sample_type() == ShellAudioBus::kFloat32) { + int16_audio_bus.Assign(audio_bus); + DCHECK_EQ(int16_audio_bus.sample_type(), ShellAudioBus::kInt16); + audio_data = + reinterpret_cast<const int16_t*>(int16_audio_bus.interleaved_data()); + } else { + DCHECK_EQ(audio_bus.sample_type(), ShellAudioBus::kInt16); + audio_data = + reinterpret_cast<const int16_t*>(audio_bus.interleaved_data()); + } + + EpStatus ep_status = EP_PRE_SPEECH; + + // Process the input data in blocks of kFrameSize, dropping any incomplete + // frames at the end (which is ok since typically the caller will be recording + // audio in multiples of our frame size). + int sample_index = 0; + while (static_cast<size_t>(sample_index + kFrameSize) <= num_samples) { + // Have the endpointer process the frame. + energy_endpointer_.ProcessAudioFrame(audio_frame_time_us_, + audio_data + sample_index, + kFrameSize, + rms_out); + sample_index += kFrameSize; + audio_frame_time_us_ += + (kFrameSize * Time::kMicrosecondsPerSecond) / sample_rate_; + + // Get the status of the endpointer. + int64_t ep_time; + ep_status = energy_endpointer_.Status(&ep_time); + + // Handle state changes. + if ((EP_SPEECH_PRESENT == ep_status) && + (EP_POSSIBLE_ONSET == old_ep_status_)) { + speech_end_time_us_ = -1; + waiting_for_speech_possibly_complete_timeout_ = false; + waiting_for_speech_complete_timeout_ = false; + // Trigger SpeechInputDidStart event on first detection. + if (false == speech_previously_detected_) { + speech_previously_detected_ = true; + speech_start_time_us_ = ep_time; + } + } + if ((EP_PRE_SPEECH == ep_status) && + (EP_POSSIBLE_OFFSET == old_ep_status_)) { + speech_end_time_us_ = ep_time; + waiting_for_speech_possibly_complete_timeout_ = true; + waiting_for_speech_complete_timeout_ = true; + } + if (ep_time > speech_input_minimum_length_us_) { + // Speech possibly complete timeout. + if ((waiting_for_speech_possibly_complete_timeout_) && + (ep_time - speech_end_time_us_ > + speech_input_possibly_complete_silence_length_us_)) { + waiting_for_speech_possibly_complete_timeout_ = false; + } + if (waiting_for_speech_complete_timeout_) { + // The length of the silence timeout period can be held constant, or it + // can be changed after a fixed amount of time from the beginning of + // speech. + bool has_stepped_silence = + (long_speech_length_us_ > 0) && + (long_speech_input_complete_silence_length_us_ > 0); + int64_t requested_silence_length; + if (has_stepped_silence && + (ep_time - speech_start_time_us_) > long_speech_length_us_) { + requested_silence_length = + long_speech_input_complete_silence_length_us_; + } else { + requested_silence_length = + speech_input_complete_silence_length_us_; + } + + // Speech complete timeout. + if ((ep_time - speech_end_time_us_) > requested_silence_length) { + waiting_for_speech_complete_timeout_ = false; + speech_input_complete_ = true; + } + } + } + old_ep_status_ = ep_status; + } + return ep_status; +} + +} // namespace content
diff --git a/src/content/browser/speech/endpointer/endpointer.h b/src/content/browser/speech/endpointer/endpointer.h new file mode 100644 index 0000000..986ed0a --- /dev/null +++ b/src/content/browser/speech/endpointer/endpointer.h
@@ -0,0 +1,157 @@ +// 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. + +#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_ +#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_ + +#include <stdint.h> + +#include "content/browser/speech/endpointer/energy_endpointer.h" +#include "media/base/shell_audio_bus.h" + +class EpStatus; + +namespace content { + +// A simple interface to the underlying energy-endpointer implementation, this +// class lets callers provide audio as being recorded and let them poll to find +// when the user has stopped speaking. +// +// There are two events that may trigger the end of speech: +// +// speechInputPossiblyComplete event: +// +// Signals that silence/noise has been detected for a *short* amount of +// time after some speech has been detected. It can be used for low latency +// UI feedback. To disable it, set it to a large amount. +// +// speechInputComplete event: +// +// This event is intended to signal end of input and to stop recording. +// The amount of time to wait after speech is set by +// speech_input_complete_silence_length_ and optionally two other +// parameters (see below). +// This time can be held constant, or can change as more speech is detected. +// In the latter case, the time changes after a set amount of time from the +// *beginning* of speech. This is motivated by the expectation that there +// will be two distinct types of inputs: short search queries and longer +// dictation style input. +// +// Three parameters are used to define the piecewise constant timeout function. +// The timeout length is speech_input_complete_silence_length until +// long_speech_length, when it changes to +// long_speech_input_complete_silence_length. +class Endpointer { + public: + typedef ::media::ShellAudioBus ShellAudioBus; + + explicit Endpointer(int sample_rate); + + // Start the endpointer. This should be called at the beginning of a session. + void StartSession(); + + // Stop the endpointer. + void EndSession(); + + // Start environment estimation. Audio will be used for environment estimation + // i.e. noise level estimation. + void SetEnvironmentEstimationMode(); + + // Start user input. This should be called when the user indicates start of + // input, e.g. by pressing a button. + void SetUserInputMode(); + + // Process a segment of audio, which may be more than one frame. + // The status of the last frame will be returned. + EpStatus ProcessAudio(const ShellAudioBus& audio_bus, float* rms_out); + + // Get the status of the endpointer. + EpStatus Status(int64_t* time_us); + + // Returns true if the endpointer detected reasonable audio levels above + // background noise which could be user speech, false if not. + bool DidStartReceivingSpeech() const { + return speech_previously_detected_; + } + + bool IsEstimatingEnvironment() const { + return energy_endpointer_.estimating_environment(); + } + + void set_speech_input_complete_silence_length(int64_t time_us) { + speech_input_complete_silence_length_us_ = time_us; + } + + void set_long_speech_input_complete_silence_length(int64_t time_us) { + long_speech_input_complete_silence_length_us_ = time_us; + } + + void set_speech_input_possibly_complete_silence_length(int64_t time_us) { + speech_input_possibly_complete_silence_length_us_ = time_us; + } + + void set_long_speech_length(int64_t time_us) { + long_speech_length_us_ = time_us; + } + + bool speech_input_complete() const { + return speech_input_complete_; + } + + int sample_rate() const { return sample_rate_; } + + // RMS background noise level in dB. + float NoiseLevelDb() const { return energy_endpointer_.GetNoiseLevelDb(); } + + private: + // Reset internal states. Helper method common to initial input utterance + // and following input utternaces. + void Reset(); + + // Minimum allowable length of speech input. + int64_t speech_input_minimum_length_us_; + + // The speechInputPossiblyComplete event signals that silence/noise has been + // detected for a *short* amount of time after some speech has been detected. + // This proporty specifies the time period. + int64_t speech_input_possibly_complete_silence_length_us_; + + // The speechInputComplete event signals that silence/noise has been + // detected for a *long* amount of time after some speech has been detected. + // This property specifies the time period. + int64_t speech_input_complete_silence_length_us_; + + // Same as above, this specifies the required silence period after speech + // detection. This period is used instead of + // speech_input_complete_silence_length_ when the utterance is longer than + // long_speech_length_. This parameter is optional. + int64_t long_speech_input_complete_silence_length_us_; + + // The period of time after which the endpointer should consider + // long_speech_input_complete_silence_length_ as a valid silence period + // instead of speech_input_complete_silence_length_. This parameter is + // optional. + int64_t long_speech_length_us_; + + // First speech onset time, used in determination of speech complete timeout. + int64_t speech_start_time_us_; + + // Most recent end time, used in determination of speech complete timeout. + int64_t speech_end_time_us_; + + int64_t audio_frame_time_us_; + EpStatus old_ep_status_; + bool waiting_for_speech_possibly_complete_timeout_; + bool waiting_for_speech_complete_timeout_; + bool speech_previously_detected_; + bool speech_input_complete_; + EnergyEndpointer energy_endpointer_; + int sample_rate_; + // 1 frame = (1 / frame_rate_) second of audio. + int frame_rate_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENDPOINTER_H_
diff --git a/src/content/browser/speech/endpointer/endpointer_unittest.cc b/src/content/browser/speech/endpointer/endpointer_unittest.cc new file mode 100644 index 0000000..929b8ad --- /dev/null +++ b/src/content/browser/speech/endpointer/endpointer_unittest.cc
@@ -0,0 +1,159 @@ +// 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 <stdint.h> + +#include "content/browser/speech/endpointer/endpointer.h" +#include "media/base/shell_audio_bus.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +const int kFrameRate = 50; // 20 ms long frames for AMR encoding. +const int kSampleRate = 8000; // 8 k samples per second for AMR encoding. + +// At 8 sample per second a 20 ms frame is 160 samples, which corrsponds +// to the AMR codec. +const int kFrameSize = kSampleRate / kFrameRate; // 160 samples. + +#if defined(OS_STARBOARD) +SB_COMPILE_ASSERT(kFrameSize == 160, invalid_frame_size); +#else +COMPILE_ASSERT(kFrameSize == 160, invalid_frame_size); +#endif // defined(OS_STARBOARD) +} // namespace + +namespace content { + +class FrameProcessor { + public: + // Process a single frame of test audio samples. + virtual EpStatus ProcessFrame(int64_t time, + int16_t* samples, + int frame_size) = 0; +}; + +void RunEndpointerEventsTest(FrameProcessor* processor) { + int16_t samples[kFrameSize]; + + // We will create a white noise signal of 150 frames. The frames from 50 to + // 100 will have more power, and the endpointer should fire on those frames. + const int kNumFrames = 150; + + // Create a random sequence of samples. + srand(1); + float gain = 0.0; + int64_t time = 0; + for (int frame_count = 0; frame_count < kNumFrames; ++frame_count) { + // The frames from 50 to 100 will have more power, and the endpointer + // should detect those frames as speech. + if ((frame_count >= 50) && (frame_count < 100)) { + gain = 2000.0; + } else { + gain = 1.0; + } + // Create random samples. + for (int i = 0; i < kFrameSize; ++i) { + float randNum = static_cast<float>(rand() - (RAND_MAX / 2)) / + static_cast<float>(RAND_MAX); + samples[i] = static_cast<int16_t>(gain * randNum); + } + + EpStatus ep_status = processor->ProcessFrame(time, samples, kFrameSize); + time += static_cast<int64_t>(kFrameSize * (1e6 / kSampleRate)); + + // Log the status. + if (20 == frame_count) + EXPECT_EQ(EP_PRE_SPEECH, ep_status); + if (70 == frame_count) + EXPECT_EQ(EP_SPEECH_PRESENT, ep_status); + if (120 == frame_count) + EXPECT_EQ(EP_PRE_SPEECH, ep_status); + } +} + +// This test instantiates and initializes a stand alone endpointer module. +// The test creates FrameData objects with random noise and send them +// to the endointer module. The energy of the first 50 frames is low, +// followed by 500 high energy frames, and another 50 low energy frames. +// We test that the correct start and end frames were detected. +class EnergyEndpointerFrameProcessor : public FrameProcessor { + public: + explicit EnergyEndpointerFrameProcessor(EnergyEndpointer* endpointer) + : endpointer_(endpointer) {} + + EpStatus ProcessFrame(int64_t time, + int16_t* samples, + int /*frame_size*/) override { + endpointer_->ProcessAudioFrame(time, samples, kFrameSize, NULL); + int64_t ep_time; + return endpointer_->Status(&ep_time); + } + + private: + EnergyEndpointer* endpointer_; +}; + +TEST(EndpointerTest, TestEnergyEndpointerEvents) { + // Initialize endpointer and configure it. We specify the parameters + // here for a 20ms window, and a 20ms step size, which corrsponds to + // the narrow band AMR codec. + EnergyEndpointerParams ep_config; + ep_config.set_frame_period(1.0f / static_cast<float>(kFrameRate)); + ep_config.set_frame_duration(1.0f / static_cast<float>(kFrameRate)); + ep_config.set_endpoint_margin(0.2f); + ep_config.set_onset_window(0.15f); + ep_config.set_speech_on_window(0.4f); + ep_config.set_offset_window(0.15f); + ep_config.set_onset_detect_dur(0.09f); + ep_config.set_onset_confirm_dur(0.075f); + ep_config.set_on_maintain_dur(0.10f); + ep_config.set_offset_confirm_dur(0.12f); + ep_config.set_decision_threshold(100.0f); + EnergyEndpointer endpointer; + endpointer.Init(ep_config); + + endpointer.StartSession(); + + EnergyEndpointerFrameProcessor frame_processor(&endpointer); + RunEndpointerEventsTest(&frame_processor); + + endpointer.EndSession(); +}; + +// Test endpointer wrapper class. +class EndpointerFrameProcessor : public FrameProcessor { + public: + typedef ::media::ShellAudioBus ShellAudioBus; + explicit EndpointerFrameProcessor(Endpointer* endpointer) + : endpointer_(endpointer) {} + + EpStatus ProcessFrame(int64_t /*time*/, + int16_t* samples, + int /*frame_size*/) override { + scoped_ptr<ShellAudioBus> frame(new ShellAudioBus(1, kFrameSize, samples)); + endpointer_->ProcessAudio(*frame, NULL); + int64_t ep_time; + return endpointer_->Status(&ep_time); + } + + private: + Endpointer* endpointer_; +}; + +TEST(EndpointerTest, TestEmbeddedEndpointerEvents) { + Endpointer endpointer(kSampleRate); + const int64_t kMillisecondsPerMicrosecond = 1000; + const int64_t short_timeout = 300 * kMillisecondsPerMicrosecond; + endpointer.set_speech_input_possibly_complete_silence_length(short_timeout); + const int64_t long_timeout = 500 * kMillisecondsPerMicrosecond; + endpointer.set_speech_input_complete_silence_length(long_timeout); + endpointer.StartSession(); + + EndpointerFrameProcessor frame_processor(&endpointer); + RunEndpointerEventsTest(&frame_processor); + + endpointer.EndSession(); +} + +} // namespace content
diff --git a/src/content/browser/speech/endpointer/energy_endpointer.cc b/src/content/browser/speech/endpointer/energy_endpointer.cc new file mode 100644 index 0000000..b8a01d5 --- /dev/null +++ b/src/content/browser/speech/endpointer/energy_endpointer.cc
@@ -0,0 +1,381 @@ +// 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. +// +// To know more about the algorithm used and the original code which this is +// based of, see +// https://wiki.corp.google.com/twiki/bin/view/Main/ChromeGoogleCodeXRef + +#include "content/browser/speech/endpointer/energy_endpointer.h" + +#include <math.h> +#include <stddef.h> + +#include "base/logging.h" + +namespace { + +// Returns the RMS (quadratic mean) of the input signal. +float RMS(const int16_t* samples, int num_samples) { + int64_t ssq_int64 = 0; + int64_t sum_int64 = 0; + for (int i = 0; i < num_samples; ++i) { + sum_int64 += samples[i]; + ssq_int64 += samples[i] * samples[i]; + } + // now convert to floats. + double sum = static_cast<double>(sum_int64); + sum /= num_samples; + double ssq = static_cast<double>(ssq_int64); + return static_cast<float>(sqrt((ssq / num_samples) - (sum * sum))); +} + +int64_t Secs2Usecs(float seconds) { + return static_cast<int64_t>(0.5 + (1.0e6 * seconds)); +} + +float GetDecibel(float value) { + if (value > 1.0e-100) + return static_cast<float>(20 * log10(value)); + return -2000.0; +} + +} // namespace + +namespace content { + +// Stores threshold-crossing histories for making decisions about the speech +// state. +class EnergyEndpointer::HistoryRing { + public: + HistoryRing() : insertion_index_(0) {} + + // Resets the ring to |size| elements each with state |initial_state| + void SetRing(int size, bool initial_state); + + // Inserts a new entry into the ring and drops the oldest entry. + void Insert(int64_t time_us, bool decision); + + // Returns the time in microseconds of the most recently added entry. + int64_t EndTime() const; + + // Returns the sum of all intervals during which 'decision' is true within + // the time in seconds specified by 'duration'. The returned interval is + // in seconds. + float RingSum(float duration_sec); + + private: + struct DecisionPoint { + int64_t time_us; + bool decision; + }; + + std::vector<DecisionPoint> decision_points_; + int insertion_index_; // Index at which the next item gets added/inserted. + + DISALLOW_COPY_AND_ASSIGN(HistoryRing); +}; + +void EnergyEndpointer::HistoryRing::SetRing(int size, bool initial_state) { + insertion_index_ = 0; + decision_points_.clear(); + DecisionPoint init = { -1, initial_state }; + decision_points_.resize(static_cast<size_t>(size), init); +} + +void EnergyEndpointer::HistoryRing::Insert(int64_t time_us, bool decision) { + decision_points_[static_cast<size_t>(insertion_index_)].time_us = time_us; + decision_points_[static_cast<size_t>(insertion_index_)].decision = decision; + insertion_index_ = + static_cast<int>((insertion_index_ + 1) % decision_points_.size()); +} + +int64_t EnergyEndpointer::HistoryRing::EndTime() const { + int ind = insertion_index_ - 1; + if (ind < 0) + ind = static_cast<int>(decision_points_.size() - 1); + return decision_points_[static_cast<size_t>(ind)].time_us; +} + +float EnergyEndpointer::HistoryRing::RingSum(float duration_sec) { + if (decision_points_.empty()) + return 0.0; + + int64_t sum_us = 0; + int ind = insertion_index_ - 1; + if (ind < 0) + ind = static_cast<int>(decision_points_.size() - 1); + int64_t end_us = decision_points_[static_cast<size_t>(ind)].time_us; + bool is_on = decision_points_[static_cast<size_t>(ind)].decision; + int64_t start_us = + end_us - static_cast<int64_t>(0.5 + (1.0e6 * duration_sec)); + if (start_us < 0) + start_us = 0; + size_t n_summed = 1; // n points ==> (n-1) intervals + while ((decision_points_[static_cast<size_t>(ind)].time_us > start_us) && + (n_summed < decision_points_.size())) { + --ind; + if (ind < 0) + ind = static_cast<int>(decision_points_.size() - 1); + if (is_on) + sum_us += end_us - decision_points_[static_cast<size_t>(ind)].time_us; + is_on = decision_points_[static_cast<size_t>(ind)].decision; + end_us = decision_points_[static_cast<size_t>(ind)].time_us; + n_summed++; + } + + return 1.0e-6f * sum_us; // Returns total time that was super threshold. +} + +EnergyEndpointer::EnergyEndpointer() + : status_(EP_PRE_SPEECH), + offset_confirm_dur_sec_(0), + endpointer_time_us_(0), + fast_update_frames_(0), + frame_counter_(0), + max_window_dur_(4.0), + sample_rate_(0), + history_(new HistoryRing()), + decision_threshold_(0), + estimating_environment_(false), + noise_level_(0), + rms_adapt_(0), + start_lag_(0), + end_lag_(0), + user_input_start_time_us_(0) { +} + +EnergyEndpointer::~EnergyEndpointer() { +} + +int EnergyEndpointer::TimeToFrame(float time) const { + return static_cast<int32_t>(0.5 + (time / params_.frame_period())); +} + +void EnergyEndpointer::Restart(bool reset_threshold) { + status_ = EP_PRE_SPEECH; + user_input_start_time_us_ = 0; + + if (reset_threshold) { + decision_threshold_ = params_.decision_threshold(); + rms_adapt_ = decision_threshold_; + noise_level_ = params_.decision_threshold() / 2.0f; + frame_counter_ = 0; // Used for rapid initial update of levels. + } + + // Set up the memories to hold the history windows. + history_->SetRing(TimeToFrame(max_window_dur_), false); + + // Flag that indicates that current input should be used for + // estimating the environment. The user has not yet started input + // by e.g. pressed the push-to-talk button. By default, this is + // false for backward compatibility. + estimating_environment_ = false; +} + +void EnergyEndpointer::Init(const EnergyEndpointerParams& params) { + params_ = params; + + // Find the longest history interval to be used, and make the ring + // large enough to accommodate that number of frames. NOTE: This + // depends upon ep_frame_period being set correctly in the factory + // that did this instantiation. + max_window_dur_ = params_.onset_window(); + if (params_.speech_on_window() > max_window_dur_) + max_window_dur_ = params_.speech_on_window(); + if (params_.offset_window() > max_window_dur_) + max_window_dur_ = params_.offset_window(); + Restart(true); + + offset_confirm_dur_sec_ = params_.offset_window() - + params_.offset_confirm_dur(); + if (offset_confirm_dur_sec_ < 0.0) + offset_confirm_dur_sec_ = 0.0; + + user_input_start_time_us_ = 0; + + // Flag that indicates that current input should be used for + // estimating the environment. The user has not yet started input + // by e.g. pressed the push-to-talk button. By default, this is + // false for backward compatibility. + estimating_environment_ = false; + // The initial value of the noise and speech levels is inconsequential. + // The level of the first frame will overwrite these values. + noise_level_ = params_.decision_threshold() / 2.0f; + fast_update_frames_ = + static_cast<int64_t>(params_.fast_update_dur() / params_.frame_period()); + + frame_counter_ = 0; // Used for rapid initial update of levels. + + sample_rate_ = params_.sample_rate(); + start_lag_ = static_cast<int>(sample_rate_ / + params_.max_fundamental_frequency()); + end_lag_ = static_cast<int>(sample_rate_ / + params_.min_fundamental_frequency()); +} + +void EnergyEndpointer::StartSession() { + Restart(true); +} + +void EnergyEndpointer::EndSession() { + status_ = EP_POST_SPEECH; +} + +void EnergyEndpointer::SetEnvironmentEstimationMode() { + Restart(true); + estimating_environment_ = true; +} + +void EnergyEndpointer::SetUserInputMode() { + estimating_environment_ = false; + user_input_start_time_us_ = endpointer_time_us_; +} + +void EnergyEndpointer::ProcessAudioFrame(int64_t time_us, + const int16_t* samples, + int num_samples, + float* rms_out) { + endpointer_time_us_ = time_us; + float rms = RMS(samples, num_samples); + + // Check that this is user input audio vs. pre-input adaptation audio. + // Input audio starts when the user indicates start of input, by e.g. + // pressing push-to-talk. Audio received prior to that is used to update + // noise and speech level estimates. + if (!estimating_environment_) { + bool decision = false; + if ((endpointer_time_us_ - user_input_start_time_us_) < + Secs2Usecs(params_.contamination_rejection_period())) { + decision = false; + DVLOG(1) << "decision: forced to false, time: " << endpointer_time_us_; + } else { + decision = (rms > decision_threshold_); + } + + history_->Insert(endpointer_time_us_, decision); + + switch (status_) { + case EP_PRE_SPEECH: + if (history_->RingSum(params_.onset_window()) > + params_.onset_detect_dur()) { + status_ = EP_POSSIBLE_ONSET; + } + break; + + case EP_POSSIBLE_ONSET: { + float tsum = history_->RingSum(params_.onset_window()); + if (tsum > params_.onset_confirm_dur()) { + status_ = EP_SPEECH_PRESENT; + } else { // If signal is not maintained, drop back to pre-speech. + if (tsum <= params_.onset_detect_dur()) + status_ = EP_PRE_SPEECH; + } + break; + } + + case EP_SPEECH_PRESENT: { + // To induce hysteresis in the state residency, we allow a + // smaller residency time in the on_ring, than was required to + // enter the SPEECH_PERSENT state. + float on_time = history_->RingSum(params_.speech_on_window()); + if (on_time < params_.on_maintain_dur()) + status_ = EP_POSSIBLE_OFFSET; + break; + } + + case EP_POSSIBLE_OFFSET: + if (history_->RingSum(params_.offset_window()) <= + offset_confirm_dur_sec_) { + // Note that this offset time may be beyond the end + // of the input buffer in a real-time system. It will be up + // to the RecognizerSession to decide what to do. + status_ = EP_PRE_SPEECH; // Automatically reset for next utterance. + } else { // If speech picks up again we allow return to SPEECH_PRESENT. + if (history_->RingSum(params_.speech_on_window()) >= + params_.on_maintain_dur()) + status_ = EP_SPEECH_PRESENT; + } + break; + + case EP_POST_SPEECH: + // fall-through + default: + LOG(WARNING) << "Invalid case in switch: " << status_; + break; + } + + // If this is a quiet, non-speech region, slowly adapt the detection + // threshold to be about 6dB above the average RMS. + if ((!decision) && (status_ == EP_PRE_SPEECH)) { + decision_threshold_ = (0.98f * decision_threshold_) + (0.02f * 2 * rms); + rms_adapt_ = decision_threshold_; + } else { + // If this is in a speech region, adapt the decision threshold to + // be about 10dB below the average RMS. If the noise level is high, + // the threshold is pushed up. + // Adaptation up to a higher level is 5 times faster than decay to + // a lower level. + if ((status_ == EP_SPEECH_PRESENT) && decision) { + if (rms_adapt_ > rms) { + rms_adapt_ = (0.99f * rms_adapt_) + (0.01f * rms); + } else { + rms_adapt_ = (0.95f * rms_adapt_) + (0.05f * rms); + } + float target_threshold = 0.3f * rms_adapt_ + noise_level_; + decision_threshold_ = (.90f * decision_threshold_) + + (0.10f * target_threshold); + } + } + + // Set a floor + if (decision_threshold_ < params_.min_decision_threshold()) + decision_threshold_ = params_.min_decision_threshold(); + } + + // Update speech and noise levels. + UpdateLevels(rms); + ++frame_counter_; + + if (rms_out) + *rms_out = GetDecibel(rms); +} + +float EnergyEndpointer::GetNoiseLevelDb() const { + return GetDecibel(noise_level_); +} + +void EnergyEndpointer::UpdateLevels(float rms) { + // Update quickly initially. We assume this is noise and that + // speech is 6dB above the noise. + if (frame_counter_ < fast_update_frames_) { + // Alpha increases from 0 to (k-1)/k where k is the number of time + // steps in the initial adaptation period. + float alpha = static_cast<float>(frame_counter_) / + static_cast<float>(fast_update_frames_); + noise_level_ = (alpha * noise_level_) + ((1 - alpha) * rms); + DVLOG(1) << "FAST UPDATE, frame_counter_ " << frame_counter_ + << ", fast_update_frames_ " << fast_update_frames_; + } else { + // Update Noise level. The noise level adapts quickly downward, but + // slowly upward. The noise_level_ parameter is not currently used + // for threshold adaptation. It is used for UI feedback. + if (noise_level_ < rms) + noise_level_ = (0.999f * noise_level_) + (0.001f * rms); + else + noise_level_ = (0.95f * noise_level_) + (0.05f * rms); + } + if (estimating_environment_ || (frame_counter_ < fast_update_frames_)) { + decision_threshold_ = noise_level_ * 2; // 6dB above noise level. + // Set a floor + if (decision_threshold_ < params_.min_decision_threshold()) + decision_threshold_ = params_.min_decision_threshold(); + } +} + +EpStatus EnergyEndpointer::Status(int64_t* status_time) const { + *status_time = history_->EndTime(); + return status_; +} + +} // namespace content
diff --git a/src/content/browser/speech/endpointer/energy_endpointer.h b/src/content/browser/speech/endpointer/energy_endpointer.h new file mode 100644 index 0000000..2d379b9 --- /dev/null +++ b/src/content/browser/speech/endpointer/energy_endpointer.h
@@ -0,0 +1,160 @@ +// 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. + +// The EnergyEndpointer class finds likely speech onset and offset points. +// +// The implementation described here is about the simplest possible. +// It is based on timings of threshold crossings for overall signal +// RMS. It is suitable for light weight applications. +// +// As written, the basic idea is that one specifies intervals that +// must be occupied by super- and sub-threshold energy levels, and +// defers decisions re onset and offset times until these +// specifications have been met. Three basic intervals are tested: an +// onset window, a speech-on window, and an offset window. We require +// super-threshold to exceed some mimimum total durations in the onset +// and speech-on windows before declaring the speech onset time, and +// we specify a required sub-threshold residency in the offset window +// before declaring speech offset. As the various residency requirements are +// met, the EnergyEndpointer instance assumes various states, and can return the +// ID of these states to the client (see EpStatus below). +// +// The levels of the speech and background noise are continuously updated. It is +// important that the background noise level be estimated initially for +// robustness in noisy conditions. The first frames are assumed to be background +// noise and a fast update rate is used for the noise level. The duration for +// fast update is controlled by the fast_update_dur_ paramter. +// +// If used in noisy conditions, the endpointer should be started and run in the +// EnvironmentEstimation mode, for at least 200ms, before switching to +// UserInputMode. +// Audio feedback contamination can appear in the input audio, if not cut +// out or handled by echo cancellation. Audio feedback can trigger a false +// accept. The false accepts can be ignored by setting +// ep_contamination_rejection_period. + +#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_ +#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_ + +#include <stdint.h> + +#include <memory> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "content/browser/speech/endpointer/energy_endpointer_params.h" + +namespace content { + +// Endpointer status codes +enum EpStatus { + EP_PRE_SPEECH = 10, + EP_POSSIBLE_ONSET, + EP_SPEECH_PRESENT, + EP_POSSIBLE_OFFSET, + EP_POST_SPEECH, +}; + +class EnergyEndpointer { + public: + // The default construction MUST be followed by Init(), before any + // other use can be made of the instance. + EnergyEndpointer(); + virtual ~EnergyEndpointer(); + + void Init(const EnergyEndpointerParams& params); + + // Start the endpointer. This should be called at the beginning of a session. + void StartSession(); + + // Stop the endpointer. + void EndSession(); + + // Start environment estimation. Audio will be used for environment estimation + // i.e. noise level estimation. + void SetEnvironmentEstimationMode(); + + // Start user input. This should be called when the user indicates start of + // input, e.g. by pressing a button. + void SetUserInputMode(); + + // Computes the next input frame and modifies EnergyEndpointer status as + // appropriate based on the computation. + void ProcessAudioFrame(int64_t time_us, + const int16_t* samples, + int num_samples, + float* rms_out); + + // Returns the current state of the EnergyEndpointer and the time + // corresponding to the most recently computed frame. + EpStatus Status(int64_t* status_time_us) const; + + bool estimating_environment() const { + return estimating_environment_; + } + + // Returns estimated noise level in dB. + float GetNoiseLevelDb() const; + + private: + class HistoryRing; + + // Resets the endpointer internal state. If reset_threshold is true, the + // state will be reset completely, including adaptive thresholds and the + // removal of all history information. + void Restart(bool reset_threshold); + + // Update internal speech and noise levels. + void UpdateLevels(float rms); + + // Returns the number of frames (or frame number) corresponding to + // the 'time' (in seconds). + int TimeToFrame(float time) const; + + EpStatus status_; // The current state of this instance. + float offset_confirm_dur_sec_; // max on time allowed to confirm POST_SPEECH + int64_t + endpointer_time_us_; // Time of the most recently received audio frame. + int64_t + fast_update_frames_; // Number of frames for initial level adaptation. + int64_t + frame_counter_; // Number of frames seen. Used for initial adaptation. + float max_window_dur_; // Largest search window size (seconds) + float sample_rate_; // Sampling rate. + + // Ring buffers to hold the speech activity history. + scoped_ptr<HistoryRing> history_; + + // Configuration parameters. + EnergyEndpointerParams params_; + + // RMS which must be exceeded to conclude frame is speech. + float decision_threshold_; + + // Flag to indicate that audio should be used to estimate environment, prior + // to receiving user input. + bool estimating_environment_; + + // Estimate of the background noise level. Used externally for UI feedback. + float noise_level_; + + // An adaptive threshold used to update decision_threshold_ when appropriate. + float rms_adapt_; + + // Start lag corresponds to the highest fundamental frequency. + int start_lag_; + + // End lag corresponds to the lowest fundamental frequency. + int end_lag_; + + // Time when mode switched from environment estimation to user input. This + // is used to time forced rejection of audio feedback contamination. + int64_t user_input_start_time_us_; + + DISALLOW_COPY_AND_ASSIGN(EnergyEndpointer); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_H_
diff --git a/src/content/browser/speech/endpointer/energy_endpointer_params.cc b/src/content/browser/speech/endpointer/energy_endpointer_params.cc new file mode 100644 index 0000000..9cdf024 --- /dev/null +++ b/src/content/browser/speech/endpointer/energy_endpointer_params.cc
@@ -0,0 +1,53 @@ +// 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 "content/browser/speech/endpointer/energy_endpointer_params.h" + +namespace content { + +EnergyEndpointerParams::EnergyEndpointerParams() { + SetDefaults(); +} + +void EnergyEndpointerParams::SetDefaults() { + frame_period_ = 0.01f; + frame_duration_ = 0.01f; + endpoint_margin_ = 0.2f; + onset_window_ = 0.15f; + speech_on_window_ = 0.4f; + offset_window_ = 0.15f; + onset_detect_dur_ = 0.09f; + onset_confirm_dur_ = 0.075f; + on_maintain_dur_ = 0.10f; + offset_confirm_dur_ = 0.12f; + decision_threshold_ = 150.0f; + min_decision_threshold_ = 50.0f; + fast_update_dur_ = 0.2f; + sample_rate_ = 8000.0f; + min_fundamental_frequency_ = 57.143f; + max_fundamental_frequency_ = 400.0f; + contamination_rejection_period_ = 0.25f; +} + +void EnergyEndpointerParams::operator=(const EnergyEndpointerParams& source) { + frame_period_ = source.frame_period(); + frame_duration_ = source.frame_duration(); + endpoint_margin_ = source.endpoint_margin(); + onset_window_ = source.onset_window(); + speech_on_window_ = source.speech_on_window(); + offset_window_ = source.offset_window(); + onset_detect_dur_ = source.onset_detect_dur(); + onset_confirm_dur_ = source.onset_confirm_dur(); + on_maintain_dur_ = source.on_maintain_dur(); + offset_confirm_dur_ = source.offset_confirm_dur(); + decision_threshold_ = source.decision_threshold(); + min_decision_threshold_ = source.min_decision_threshold(); + fast_update_dur_ = source.fast_update_dur(); + sample_rate_ = source.sample_rate(); + min_fundamental_frequency_ = source.min_fundamental_frequency(); + max_fundamental_frequency_ = source.max_fundamental_frequency(); + contamination_rejection_period_ = source.contamination_rejection_period(); +} + +} // namespace content
diff --git a/src/content/browser/speech/endpointer/energy_endpointer_params.h b/src/content/browser/speech/endpointer/energy_endpointer_params.h new file mode 100644 index 0000000..303b3b0 --- /dev/null +++ b/src/content/browser/speech/endpointer/energy_endpointer_params.h
@@ -0,0 +1,135 @@ +// 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. + +#ifndef CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_ +#define CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_ + +namespace content { + +// Input parameters for the EnergyEndpointer class. +class EnergyEndpointerParams { + public: + EnergyEndpointerParams(); + + void SetDefaults(); + + void operator=(const EnergyEndpointerParams& source); + + // Accessors and mutators + float frame_period() const { return frame_period_; } + void set_frame_period(float frame_period) { + frame_period_ = frame_period; + } + + float frame_duration() const { return frame_duration_; } + void set_frame_duration(float frame_duration) { + frame_duration_ = frame_duration; + } + + float endpoint_margin() const { return endpoint_margin_; } + void set_endpoint_margin(float endpoint_margin) { + endpoint_margin_ = endpoint_margin; + } + + float onset_window() const { return onset_window_; } + void set_onset_window(float onset_window) { onset_window_ = onset_window; } + + float speech_on_window() const { return speech_on_window_; } + void set_speech_on_window(float speech_on_window) { + speech_on_window_ = speech_on_window; + } + + float offset_window() const { return offset_window_; } + void set_offset_window(float offset_window) { + offset_window_ = offset_window; + } + + float onset_detect_dur() const { return onset_detect_dur_; } + void set_onset_detect_dur(float onset_detect_dur) { + onset_detect_dur_ = onset_detect_dur; + } + + float onset_confirm_dur() const { return onset_confirm_dur_; } + void set_onset_confirm_dur(float onset_confirm_dur) { + onset_confirm_dur_ = onset_confirm_dur; + } + + float on_maintain_dur() const { return on_maintain_dur_; } + void set_on_maintain_dur(float on_maintain_dur) { + on_maintain_dur_ = on_maintain_dur; + } + + float offset_confirm_dur() const { return offset_confirm_dur_; } + void set_offset_confirm_dur(float offset_confirm_dur) { + offset_confirm_dur_ = offset_confirm_dur; + } + + float decision_threshold() const { return decision_threshold_; } + void set_decision_threshold(float decision_threshold) { + decision_threshold_ = decision_threshold; + } + + float min_decision_threshold() const { return min_decision_threshold_; } + void set_min_decision_threshold(float min_decision_threshold) { + min_decision_threshold_ = min_decision_threshold; + } + + float fast_update_dur() const { return fast_update_dur_; } + void set_fast_update_dur(float fast_update_dur) { + fast_update_dur_ = fast_update_dur; + } + + float sample_rate() const { return sample_rate_; } + void set_sample_rate(float sample_rate) { sample_rate_ = sample_rate; } + + float min_fundamental_frequency() const { return min_fundamental_frequency_; } + void set_min_fundamental_frequency(float min_fundamental_frequency) { + min_fundamental_frequency_ = min_fundamental_frequency; + } + + float max_fundamental_frequency() const { return max_fundamental_frequency_; } + void set_max_fundamental_frequency(float max_fundamental_frequency) { + max_fundamental_frequency_ = max_fundamental_frequency; + } + + float contamination_rejection_period() const { + return contamination_rejection_period_; + } + void set_contamination_rejection_period( + float contamination_rejection_period) { + contamination_rejection_period_ = contamination_rejection_period; + } + + private: + float frame_period_; // Frame period + float frame_duration_; // Window size + float onset_window_; // Interval scanned for onset activity + float speech_on_window_; // Inverval scanned for ongoing speech + float offset_window_; // Interval scanned for offset evidence + float offset_confirm_dur_; // Silence duration required to confirm offset + float decision_threshold_; // Initial rms detection threshold + float min_decision_threshold_; // Minimum rms detection threshold + float fast_update_dur_; // Period for initial estimation of levels. + float sample_rate_; // Expected sample rate. + + // Time to add on either side of endpoint threshold crossings + float endpoint_margin_; + // Total dur within onset_window required to enter ONSET state + float onset_detect_dur_; + // Total on time within onset_window required to enter SPEECH_ON state + float onset_confirm_dur_; + // Minimum dur in SPEECH_ON state required to maintain ON state + float on_maintain_dur_; + // Minimum fundamental frequency for autocorrelation. + float min_fundamental_frequency_; + // Maximum fundamental frequency for autocorrelation. + float max_fundamental_frequency_; + // Period after start of user input that above threshold values are ignored. + // This is to reject audio feedback contamination. + float contamination_rejection_period_; +}; + +} // namespace content + +#endif // CONTENT_BROWSER_SPEECH_ENDPOINTER_ENERGY_ENDPOINTER_PARAMS_H_
diff --git a/src/content/browser/speech/speech.gyp b/src/content/browser/speech/speech.gyp new file mode 100644 index 0000000..2db9fdf --- /dev/null +++ b/src/content/browser/speech/speech.gyp
@@ -0,0 +1,60 @@ +# Copyright 2016 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. + +{ + 'targets': [ + { + 'target_name': 'speech', + 'type': 'static_library', + 'sources': [ + 'chunked_byte_buffer.cc', + 'chunked_byte_buffer.h', + 'endpointer/endpointer.cc', + 'endpointer/endpointer.h', + 'endpointer/energy_endpointer.cc', + 'endpointer/energy_endpointer.h', + 'endpointer/energy_endpointer_params.cc', + 'endpointer/energy_endpointer_params.h', + ], + 'dependencies': [ + '<(DEPTH)/media/media.gyp:media', + ], + }, + { + 'target_name': 'speech_test', + 'type': '<(gtest_target_type)', + 'sources': [ + 'chunked_byte_buffer_unittest.cc', + 'endpointer/endpointer_unittest.cc', + ], + 'dependencies': [ + 'speech', + '<(DEPTH)/base/base.gyp:run_all_unittests', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + }, + + { + 'target_name': 'speech_test_deploy', + 'type': 'none', + 'dependencies': [ + 'speech_test', + ], + 'variables': { + 'executable_name': 'speech_test', + }, + 'includes': [ '../../../starboard/build/deploy.gypi' ], + }, + ], +}
diff --git a/src/glimp/egl/surface_impl.h b/src/glimp/egl/surface_impl.h index ead72c4..fc026c3 100644 --- a/src/glimp/egl/surface_impl.h +++ b/src/glimp/egl/surface_impl.h
@@ -32,6 +32,10 @@ virtual int GetWidth() const = 0; virtual int GetHeight() const = 0; + // Returns true if the surface is a window surface, false if the surface is a + // pixel buffer or a pixmap. + virtual bool IsWindowSurface() const = 0; + private: };
diff --git a/src/glimp/entry_points/gles_2_0.cc b/src/glimp/entry_points/gles_2_0.cc index 10391ca..884318a 100644 --- a/src/glimp/entry_points/gles_2_0.cc +++ b/src/glimp/entry_points/gles_2_0.cc
@@ -281,7 +281,12 @@ } void GL_APIENTRY glCullFace(GLenum mode) { - SB_NOTIMPLEMENTED(); + gles::Context* context = GetCurrentContext(); + if (!context) { + return; + } + + return context->CullFace(mode); } void GL_APIENTRY glDeleteBuffers(GLsizei n, const GLuint* buffers) {
diff --git a/src/glimp/gles/buffer.cc b/src/glimp/gles/buffer.cc index c0975cd..5e03c06 100644 --- a/src/glimp/gles/buffer.cc +++ b/src/glimp/gles/buffer.cc
@@ -38,12 +38,7 @@ } // namespace Buffer::Buffer(nb::scoped_ptr<BufferImpl> impl) - : impl_(impl.Pass()), target_valid_(false), size_in_bytes_(0) {} - -void Buffer::SetTarget(GLenum target) { - target_ = target; - target_valid_ = true; -} + : impl_(impl.Pass()), size_in_bytes_(0) {} void Buffer::Allocate(GLenum usage, size_t size) { size_in_bytes_ = size; @@ -51,7 +46,6 @@ } void Buffer::SetData(GLintptr offset, GLsizeiptr size, const GLvoid* data) { - SB_DCHECK(target_valid()); SB_DCHECK(size_in_bytes_ >= offset + size); SB_DCHECK(offset >= 0); SB_DCHECK(size >= 0);
diff --git a/src/glimp/gles/buffer.h b/src/glimp/gles/buffer.h index 71b18ce..85cb462 100644 --- a/src/glimp/gles/buffer.h +++ b/src/glimp/gles/buffer.h
@@ -30,9 +30,6 @@ public: explicit Buffer(nb::scoped_ptr<BufferImpl> impl); - // Called when glBindBuffer() is called. - void SetTarget(GLenum target); - // Allocates memory within this Buffer object. void Allocate(GLenum usage, size_t size); @@ -47,16 +44,6 @@ // Returns true if the buffer is currently mapped to the CPU address space. bool is_mapped() const { return is_mapped_; } - // Returns true if the target has been set (e.g. via glBindBuffer()). - bool target_valid() const { return target_valid_; } - - // Returns the target (set via glBindBuffer()). Must be called only if - // target_valid() is true. - GLenum target() const { - SB_DCHECK(target_valid_); - return target_; - } - GLsizeiptr size_in_bytes() const { return size_in_bytes_; } BufferImpl* impl() const { return impl_.get(); } @@ -67,13 +54,6 @@ nb::scoped_ptr<BufferImpl> impl_; - // The target type this buffer was last bound as, set through a call to - // glBindBuffer(). - GLenum target_; - - // Represents whether or not target_ as been initialized yet. - bool target_valid_; - // The size of the allocated memory used by this buffer. GLsizeiptr size_in_bytes_;
diff --git a/src/glimp/gles/context.cc b/src/glimp/gles/context.cc index a104230..65b7f47 100644 --- a/src/glimp/gles/context.cc +++ b/src/glimp/gles/context.cc
@@ -21,6 +21,7 @@ #include "glimp/egl/error.h" #include "glimp/egl/surface.h" #include "glimp/gles/blend_state.h" +#include "glimp/gles/cull_face_state.h" #include "glimp/gles/draw_mode.h" #include "glimp/gles/index_data_type.h" #include "glimp/gles/pixel_format.h" @@ -299,9 +300,13 @@ draw_state_.scissor.enabled = true; draw_state_dirty_flags_.scissor_dirty = true; break; + case GL_CULL_FACE: + draw_state_.cull_face_state.enabled = true; + draw_state_.cull_face_state.mode = CullFaceState::kBack; + draw_state_dirty_flags_.cull_face_dirty = true; + break; case GL_DEPTH_TEST: case GL_DITHER: - case GL_CULL_FACE: case GL_STENCIL_TEST: case GL_POLYGON_OFFSET_FILL: case GL_SAMPLE_ALPHA_TO_COVERAGE: @@ -323,9 +328,12 @@ draw_state_.scissor.enabled = false; draw_state_dirty_flags_.scissor_dirty = true; break; + case GL_CULL_FACE: + draw_state_.cull_face_state.enabled = false; + draw_state_dirty_flags_.cull_face_dirty = true; + break; case GL_DEPTH_TEST: case GL_DITHER: - case GL_CULL_FACE: case GL_STENCIL_TEST: case GL_POLYGON_OFFSET_FILL: case GL_SAMPLE_ALPHA_TO_COVERAGE: @@ -426,6 +434,31 @@ draw_state_dirty_flags_.blend_state_dirty = true; } +namespace { +CullFaceState::Mode CullFaceModeFromEnum(GLenum mode) { + switch (mode) { + case GL_FRONT: + return CullFaceState::kFront; + case GL_BACK: + return CullFaceState::kBack; + case GL_FRONT_AND_BACK: + return CullFaceState::kFrontAndBack; + default: + return CullFaceState::kModeInvalid; + } +} +} // namespace + +void Context::CullFace(GLenum mode) { + CullFaceState::Mode cull_face_mode = CullFaceModeFromEnum(mode); + if (cull_face_mode == CullFaceState::kModeInvalid) { + SetError(GL_INVALID_ENUM); + return; + } + draw_state_.cull_face_state.mode = cull_face_mode; + draw_state_dirty_flags_.cull_face_dirty = true; +} + GLuint Context::CreateProgram() { GLIMP_TRACE_EVENT0(__FUNCTION__); nb::scoped_ptr<ProgramImpl> program_impl = impl_->CreateProgram(); @@ -661,10 +694,18 @@ buffer_object->Unmap(); } - // If a bound buffer is deleted, set the bound buffer to NULL. - if (buffer_object->target_valid()) { - *GetBoundBufferForTarget(buffer_object->target()) = - nb::scoped_refptr<Buffer>(); + // If a bound buffer is deleted, set the bound buffer to NULL. The buffer + // may be bound to any target, therefore we must scan them all. + const GLenum buffer_targets[3] = {GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, + GL_PIXEL_UNPACK_BUFFER}; + for (int target_index = 0; target_index < SB_ARRAY_SIZE(buffer_targets); + ++target_index) { + GLenum target = buffer_targets[target_index]; + nb::scoped_refptr<Buffer>* bound_buffer = GetBoundBufferForTarget(target); + SB_DCHECK(bound_buffer); + if ((*bound_buffer).get() == buffer_object.get()) { + *bound_buffer = NULL; + } } } } @@ -697,25 +738,20 @@ return; } - if (buffer == 0) { - // Unbind the current buffer if 0 is passed in for buffer. - *GetBoundBufferForTarget(target) = NULL; - return; + nb::scoped_refptr<Buffer>* bound_buffer = GetBoundBufferForTarget(target); + SB_DCHECK(bound_buffer); + nb::scoped_refptr<Buffer> buffer_object; + if (buffer != 0) { + buffer_object = resource_manager_->GetBuffer(buffer); + if (!buffer_object) { + // The buffer to be bound is invalid. + SB_NOTIMPLEMENTED() + << "Creating buffers with glBindBuffer () not supported"; + return; + } } - nb::scoped_refptr<Buffer> buffer_object = - resource_manager_->GetBuffer(buffer); - - if (!buffer_object) { - // According to the specification, no error is generated if the buffer is - // invalid. - // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindBuffer.xml - SB_DLOG(WARNING) << "Could not glBindBuffer() to invalid buffer."; - return; - } - - buffer_object->SetTarget(target); - *GetBoundBufferForTarget(target) = buffer_object; + *bound_buffer = buffer_object; } void Context::BufferData(GLenum target, @@ -961,11 +997,12 @@ return NULL; } -nb::scoped_refptr<Texture>* Context::GetBoundTextureForTarget(GLenum target) { +nb::scoped_refptr<Texture>* Context::GetBoundTextureForTarget(GLenum target, + GLenum texture) { GLIMP_TRACE_EVENT0(__FUNCTION__); switch (target) { case GL_TEXTURE_2D: - return &(texture_units_[active_texture_ - GL_TEXTURE0]); + return &(texture_units_[texture - GL_TEXTURE0]); case GL_TEXTURE_CUBE_MAP: SB_NOTREACHED() << "Currently unimplemented in glimp."; return NULL; @@ -1025,7 +1062,6 @@ // Silently ignore 0 textures. continue; } - nb::scoped_refptr<Texture> texture_object = resource_manager_->DeregisterTexture(textures[i]); @@ -1036,10 +1072,18 @@ return; } - // If a bound texture is deleted, set the bound texture to NULL. - if (texture_object->target_valid()) { - *GetBoundTextureForTarget(texture_object->target()) = NULL; - enabled_textures_dirty_ = true; + // If a bound texture is deleted, set the bound texture to NULL. The texture + // may be bound to multiple texture units, including texture units that are + // not active, therefore we must scan them all. + for (int texture_index = 0; + texture_index < impl_->GetMaxFragmentTextureUnits(); ++texture_index) { + GLenum texture_unit = texture_index + GL_TEXTURE0; + nb::scoped_refptr<Texture>* bound_texture = + GetBoundTextureForTarget(GL_TEXTURE_2D, texture_unit); + if ((*bound_texture).get() == texture_object.get()) { + enabled_textures_dirty_ = true; + *bound_texture = NULL; + } } } } @@ -1062,25 +1106,26 @@ return; } - if (texture == 0) { - // Unbind the current texture if 0 is passed in for texture. - *GetBoundTextureForTarget(target) = NULL; - enabled_textures_dirty_ = true; - return; + nb::scoped_refptr<Texture>* bound_texture = + GetBoundTextureForTarget(target, active_texture_); + SB_DCHECK(bound_texture); + nb::scoped_refptr<Texture> texture_object; + if (texture != 0) { + texture_object = resource_manager_->GetTexture(texture); + if (!texture_object) { + // The texture to be bound is invalid. + SB_NOTIMPLEMENTED() + << "Creating textures with glBindTexture() not supported"; + return; + } } - nb::scoped_refptr<Texture> texture_object = - resource_manager_->GetTexture(texture); - - if (!texture_object) { - // According to the specification, no error is generated if the texture is - // invalid. - // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindTexture.xml + if ((*bound_texture).get() == texture_object.get()) { + // The new texture being bound is the same as the already the bound + // texture. return; } - - texture_object->SetTarget(target); - *GetBoundTextureForTarget(target) = texture_object; + *bound_texture = texture_object; enabled_textures_dirty_ = true; } @@ -1131,8 +1176,8 @@ void Context::TexParameteri(GLenum target, GLenum pname, GLint param) { GLIMP_TRACE_EVENT0(__FUNCTION__); - Sampler* active_sampler = - (*GetBoundTextureForTarget(target))->sampler_parameters(); + Sampler* active_sampler = (*GetBoundTextureForTarget(target, active_texture_)) + ->sampler_parameters(); switch (pname) { case GL_TEXTURE_MAG_FILTER: { @@ -1264,7 +1309,8 @@ SB_DCHECK(pixel_format != kPixelFormatInvalid) << "Pixel format not supported by glimp."; - nb::scoped_refptr<Texture> texture_object = *GetBoundTextureForTarget(target); + nb::scoped_refptr<Texture> texture_object = + *GetBoundTextureForTarget(target, active_texture_); if (!texture_object) { // According to the specification, no error is generated if no texture // is bound. @@ -1332,7 +1378,8 @@ SB_DCHECK(pixel_format != kPixelFormatInvalid) << "Pixel format not supported by glimp."; - nb::scoped_refptr<Texture> texture_object = *GetBoundTextureForTarget(target); + nb::scoped_refptr<Texture> texture_object = + *GetBoundTextureForTarget(target, active_texture_); if (!texture_object) { // According to the specification, no error is generated if no texture // is bound. @@ -2083,8 +2130,14 @@ void Context::SwapBuffers() { GLIMP_TRACE_EVENT0(__FUNCTION__); - Flush(); - impl_->SwapBuffers(default_draw_framebuffer_->color_attachment_surface()); + egl::Surface* surface = default_draw_framebuffer_->color_attachment_surface(); + // If surface is a pixel buffer or a pixmap, eglSwapBuffers has no effect, and + // no error is generated. + // https://www.khronos.org/registry/egl/sdk/docs/man/html/eglSwapBuffers.xhtml + if (surface->impl()->IsWindowSurface()) { + Flush(); + impl_->SwapBuffers(surface); + } } bool Context::BindTextureToEGLSurface(egl::Surface* surface) { @@ -2092,7 +2145,7 @@ SB_DCHECK(surface->GetTextureTarget() == EGL_TEXTURE_2D); const nb::scoped_refptr<Texture>& current_texture = - *GetBoundTextureForTarget(GL_TEXTURE_2D); + *GetBoundTextureForTarget(GL_TEXTURE_2D, active_texture_); if (!current_texture) { SB_DLOG(WARNING) << "No texture is currently bound during call to "
diff --git a/src/glimp/gles/context.h b/src/glimp/gles/context.h index e028ed5..018a2f2 100644 --- a/src/glimp/gles/context.h +++ b/src/glimp/gles/context.h
@@ -95,6 +95,8 @@ void BlendFunc(GLenum sfactor, GLenum dfactor); + void CullFace(GLenum mode); + GLuint CreateProgram(); void DeleteProgram(GLuint program); void AttachShader(GLuint program, GLuint shader); @@ -232,15 +234,16 @@ void ReleaseContext(); void SetError(GLenum error) { error_ = error; } - // Returns the bound buffer slot for the specific specified target. + // Returns the bound buffer for the specific specified target. // This returns a pointer because it is used by glBindBuffer() which modifies // what the returned scoped_refptr points to. nb::scoped_refptr<Buffer>* GetBoundBufferForTarget(GLenum target); - // Returns the bound texture slot for the specific specified target. + // Returns the bound texture for the specific specified target and slot. // This returns a pointer because it is used by glBindTexture() which modifies // what the returned scoped_refptr points to. - nb::scoped_refptr<Texture>* GetBoundTextureForTarget(GLenum target); + nb::scoped_refptr<Texture>* GetBoundTextureForTarget(GLenum target, + GLenum texture); void SetupExtensionsString();
diff --git a/src/glimp/gles/cull_face_state.h b/src/glimp/gles/cull_face_state.h new file mode 100644 index 0000000..b68d6b0 --- /dev/null +++ b/src/glimp/gles/cull_face_state.h
@@ -0,0 +1,45 @@ +/* + * Copyright 2016 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 GLIMP_GLES_CULL_FACE_STATE_H_ +#define GLIMP_GLES_CULL_FACE_STATE_H_ + +namespace glimp { +namespace gles { + +// Describes GL cull face state, which can be modified via commands like +// glCullFace(). +// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glCullFace.xml +struct CullFaceState { + // When enabled, the initial face culling value should be kBack. + enum Mode { + kFront, + kBack, + kFrontAndBack, + kModeInvalid, + }; + + // Default state is face culling off. + CullFaceState() : enabled(false), mode(kBack) {} + + Mode mode; + bool enabled; +}; + +} // namespace gles +} // namespace glimp + +#endif // GLIMP_GLES_CULL_FACE_STATE_H_
diff --git a/src/glimp/gles/draw_state.h b/src/glimp/gles/draw_state.h index 96f5ec1..db9c2d9 100644 --- a/src/glimp/gles/draw_state.h +++ b/src/glimp/gles/draw_state.h
@@ -24,6 +24,7 @@ #include "glimp/egl/surface.h" #include "glimp/gles/blend_state.h" #include "glimp/gles/buffer.h" +#include "glimp/gles/cull_face_state.h" #include "glimp/gles/framebuffer.h" #include "glimp/gles/program.h" #include "glimp/gles/sampler.h" @@ -156,6 +157,9 @@ // existing pixels in the output framebuffer. BlendState blend_state; + // Defines whether face culling is enabled, and upon which face if so. + CullFaceState cull_face_state; + // The currently bound array buffer, set by calling // glBindBuffer(GL_ARRAY_BUFFER). nb::scoped_refptr<Buffer> array_buffer; @@ -190,6 +194,7 @@ scissor_dirty = true; viewport_dirty = true; blend_state_dirty = true; + cull_face_dirty = true; array_buffer_dirty = true; element_array_buffer_dirty = true; used_program_dirty = true; @@ -205,6 +210,7 @@ bool scissor_dirty; bool viewport_dirty; bool blend_state_dirty; + bool cull_face_dirty; bool array_buffer_dirty; bool element_array_buffer_dirty; bool used_program_dirty;
diff --git a/src/glimp/gles/program.cc b/src/glimp/gles/program.cc index ff02fbe..0309920 100644 --- a/src/glimp/gles/program.cc +++ b/src/glimp/gles/program.cc
@@ -163,7 +163,7 @@ // Clear all stored uniform information and values. void Program::ClearUniforms() { for (size_t i = 0; i < uniforms_.size(); ++i) { - SbMemoryFree(uniforms_[i].data); + SbMemoryDeallocate(uniforms_[i].data); } uniforms_.clear(); active_uniform_locations_.clear(); @@ -206,7 +206,7 @@ // We need to reallocate data if the information has changed. uniform->info = new_info; - SbMemoryFree(uniform->data); + SbMemoryDeallocate(uniform->data); uniform->data = SbMemoryAllocate(DataSizeForType(count, elem_size, type)); } SbMemoryCopy(uniform->data, v, DataSizeForType(count, elem_size, type));
diff --git a/src/glimp/gles/texture.cc b/src/glimp/gles/texture.cc index a057f74..0e1ea8c 100644 --- a/src/glimp/gles/texture.cc +++ b/src/glimp/gles/texture.cc
@@ -24,15 +24,9 @@ Texture::Texture(nb::scoped_ptr<TextureImpl> impl) : impl_(impl.Pass()), - target_valid_(false), texture_allocated_(false), bound_to_surface_(NULL) {} -void Texture::SetTarget(GLenum target) { - target_ = target; - target_valid_ = true; -} - void Texture::Initialize(GLint level, PixelFormat pixel_format, GLsizei width,
diff --git a/src/glimp/gles/texture.h b/src/glimp/gles/texture.h index 1d4bcc5..9c530b4 100644 --- a/src/glimp/gles/texture.h +++ b/src/glimp/gles/texture.h
@@ -34,9 +34,6 @@ public: explicit Texture(nb::scoped_ptr<TextureImpl> impl); - // Called when glBindTexture() is called. - void SetTarget(GLenum target); - void Initialize(GLint level, PixelFormat pixel_format, GLsizei width, @@ -82,16 +79,6 @@ // or not. bool CanBeAttachedToFramebuffer() const; - // Returns true if the target has been set (e.g. via glBindTexture()). - bool target_valid() const { return target_valid_; } - - // Returns the target (set via glBindTexture()). Must be called only if - // target_valid() is true. - GLenum target() const { - SB_DCHECK(target_valid_); - return target_; - } - TextureImpl* impl() const { return impl_.get(); } // Returns whether the data has been set yet or not. @@ -121,13 +108,6 @@ nb::scoped_ptr<TextureImpl> impl_; - // The target type this texture was last bound as, set through a call to - // glBindTexture(). - GLenum target_; - - // Represents whether or not target_ as been initialized yet. - bool target_valid_; - // True if underlying texture data has been allocated yet or not (e.g. // will be true after glTexImage2D() is called.) bool texture_allocated_;
diff --git a/src/glimp/glimp_common.gypi b/src/glimp/glimp_common.gypi index 4f00bfe..ef1a68c 100644 --- a/src/glimp/glimp_common.gypi +++ b/src/glimp/glimp_common.gypi
@@ -45,6 +45,7 @@ 'gles/context_impl.h', 'gles/convert_pixel_data.cc', 'gles/convert_pixel_data.h', + 'gles/cull_face_state.h', 'gles/draw_mode.h', 'gles/draw_state.cc', 'gles/draw_state.h',
diff --git a/src/media/audio/shell_audio_streamer_starboard.cc b/src/media/audio/shell_audio_streamer_starboard.cc index ddcc139..22a143f 100644 --- a/src/media/audio/shell_audio_streamer_starboard.cc +++ b/src/media/audio/shell_audio_streamer_starboard.cc
@@ -14,9 +14,7 @@ * limitations under the License. */ -#include "media/audio/null_audio_streamer.h" - -#include "base/logging.h" +#include "media/audio/shell_audio_streamer.h" namespace media { @@ -25,7 +23,7 @@ void ShellAudioStreamer::Terminate() {} ShellAudioStreamer* ShellAudioStreamer::Instance() { - return NullAudioStreamer::GetInstance(); + return NULL; } } // namespace media
diff --git a/src/media/base/sbplayer_pipeline.cc b/src/media/base/sbplayer_pipeline.cc index 64497b8..ff21883 100644 --- a/src/media/base/sbplayer_pipeline.cc +++ b/src/media/base/sbplayer_pipeline.cc
@@ -521,8 +521,8 @@ return; } - if (error != PIPELINE_OK && !error_cb_.is_null()) { - base::ResetAndReturn(&error_cb_).Run(error); + if (error != PIPELINE_OK) { + ResetAndRunIfNotNull(&error_cb_, error); } } @@ -595,9 +595,13 @@ DCHECK(message_loop_->BelongsToCurrentThread()); if (status != PIPELINE_OK) { - if (!error_cb_.is_null()) { - base::ResetAndReturn(&error_cb_).Run(status); - } + ResetAndRunIfNotNull(&error_cb_, status); + return; + } + + if (demuxer_->GetStream(DemuxerStream::AUDIO) == NULL || + demuxer_->GetStream(DemuxerStream::VIDEO) == NULL) { + ResetAndRunIfNotNull(&error_cb_, DEMUXER_ERROR_NO_SUPPORTED_STREAMS); return; } @@ -757,9 +761,7 @@ case kSbPlayerStateDestroyed: break; case kSbPlayerStateError: - if (!error_cb_.is_null()) { - base::ResetAndReturn(&error_cb_).Run(PIPELINE_ERROR_DECODE); - } + ResetAndRunIfNotNull(&error_cb_, PIPELINE_ERROR_DECODE); break; } }
diff --git a/src/media/base/shell_audio_bus.cc b/src/media/base/shell_audio_bus.cc index 86f4129..4b5ad5e 100644 --- a/src/media/base/shell_audio_bus.cc +++ b/src/media/base/shell_audio_bus.cc
@@ -23,6 +23,9 @@ namespace { +typedef ShellAudioBus::StorageType StorageType; +typedef ShellAudioBus::SampleType SampleType; + const float kFloat32ToInt16Factor = 32768.f; inline void ConvertSample(ShellAudioBus::SampleType src_type, @@ -218,66 +221,86 @@ } } -template <ShellAudioBus::StorageType T> -inline uint8* ShellAudioBus::GetSamplePtrForType(size_t channel, - size_t frame) const { - DCHECK_LT(channel, channels_); - DCHECK_LT(frame, frames_); - - if (T == kInterleaved) { - return channel_data_[0] + sizeof(float) * (channels_ * frame + channel); - } else if (T == kPlanar) { - return channel_data_[channel] + sizeof(float) * frame; - } else { - NOTREACHED(); - } - - return NULL; -} - -template <ShellAudioBus::StorageType T> -inline float ShellAudioBus::GetFloat32SampleForType(size_t channel, - size_t frame) const { - return *reinterpret_cast<const float*>( - GetSamplePtrForType<T>(channel, frame)); -} - -template <ShellAudioBus::StorageType SourceStorageType, - ShellAudioBus::StorageType DestStorageType> -void ShellAudioBus::MixForType(const ShellAudioBus& source) { +template <typename SourceSampleType, + typename DestSampleType, + StorageType SourceStorageType, + StorageType DestStorageType> +void ShellAudioBus::MixForTypes(const ShellAudioBus& source) { const size_t frames = std::min(frames_, source.frames_); for (size_t channel = 0; channel < channels_; ++channel) { for (size_t frame = 0; frame < frames; ++frame) { - *reinterpret_cast<float*>( - GetSamplePtrForType<DestStorageType>(channel, frame)) += - source.GetFloat32SampleForType<SourceStorageType>(channel, frame); + *reinterpret_cast<DestSampleType*>( + GetSamplePtrForType<DestSampleType, DestStorageType>(channel, + frame)) += + source.GetSampleForType<SourceSampleType, SourceStorageType>(channel, + frame); } } } void ShellAudioBus::Mix(const ShellAudioBus& source) { DCHECK_EQ(channels_, source.channels_); - DCHECK_EQ(sample_type_, kFloat32); - DCHECK_EQ(source.sample_type_, kFloat32); - if (channels_ != source.channels_ || sample_type_ != kFloat32 || - source.sample_type_ != kFloat32) { + + if (channels_ != source.channels_) { ZeroAllFrames(); return; } // Profiling has identified this area of code as hot, so instead of calling - // GetSamplePtr, which branches on storage_type_ each time it is called, we - // branch once before we loop and inline the branch of the function we want. - DCHECK_EQ(GetSampleSizeInBytes(), sizeof(float)); - if (source.storage_type_ == kInterleaved && storage_type_ == kInterleaved) { - MixForType<kInterleaved, kInterleaved>(source); - } else if (source.storage_type_ == kInterleaved && storage_type_ == kPlanar) { - MixForType<kInterleaved, kPlanar>(source); - } else if (source.storage_type_ == kPlanar && storage_type_ == kInterleaved) { - MixForType<kPlanar, kInterleaved>(source); - } else if (source.storage_type_ == kPlanar && storage_type_ == kPlanar) { - MixForType<kPlanar, kPlanar>(source); + // GetSamplePtr, which branches each time it is called, we branch once + // before we loop and inline the branch of the function we want. + if (source.sample_type_ == kInt16 && sample_type_ == kInt16 && + source.storage_type_ == kInterleaved && storage_type_ == kInterleaved) { + MixForTypes<int16, int16, kInterleaved, kInterleaved>(source); + } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 && + source.storage_type_ == kInterleaved && storage_type_ == kPlanar) { + MixForTypes<int16, int16, kInterleaved, kPlanar>(source); + } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 && + source.storage_type_ == kPlanar && storage_type_ == kInterleaved) { + MixForTypes<int16, int16, kPlanar, kInterleaved>(source); + } else if (source.sample_type_ == kInt16 && sample_type_ == kInt16 && + source.storage_type_ == kPlanar && storage_type_ == kPlanar) { + MixForTypes<int16, int16, kPlanar, kPlanar>(source); + } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 && + source.storage_type_ == kInterleaved && + storage_type_ == kInterleaved) { + MixForTypes<int16, float, kInterleaved, kInterleaved>(source); + } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 && + source.storage_type_ == kInterleaved && storage_type_ == kPlanar) { + MixForTypes<int16, float, kInterleaved, kPlanar>(source); + } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 && + source.storage_type_ == kPlanar && storage_type_ == kInterleaved) { + MixForTypes<int16, float, kPlanar, kInterleaved>(source); + } else if (source.sample_type_ == kInt16 && sample_type_ == kFloat32 && + source.storage_type_ == kPlanar && storage_type_ == kPlanar) { + MixForTypes<int16, float, kPlanar, kPlanar>(source); + } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 && + source.storage_type_ == kInterleaved && + storage_type_ == kInterleaved) { + MixForTypes<float, int16, kInterleaved, kInterleaved>(source); + } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 && + source.storage_type_ == kInterleaved && storage_type_ == kPlanar) { + MixForTypes<float, int16, kInterleaved, kPlanar>(source); + } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 && + source.storage_type_ == kPlanar && storage_type_ == kInterleaved) { + MixForTypes<float, int16, kPlanar, kInterleaved>(source); + } else if (source.sample_type_ == kFloat32 && sample_type_ == kInt16 && + source.storage_type_ == kPlanar && storage_type_ == kPlanar) { + MixForTypes<float, int16, kPlanar, kPlanar>(source); + } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 && + source.storage_type_ == kInterleaved && + storage_type_ == kInterleaved) { + MixForTypes<float, float, kInterleaved, kInterleaved>(source); + } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 && + source.storage_type_ == kInterleaved && storage_type_ == kPlanar) { + MixForTypes<float, float, kInterleaved, kPlanar>(source); + } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 && + source.storage_type_ == kPlanar && storage_type_ == kInterleaved) { + MixForTypes<float, float, kPlanar, kInterleaved>(source); + } else if (source.sample_type_ == kFloat32 && sample_type_ == kFloat32 && + source.storage_type_ == kPlanar && storage_type_ == kPlanar) { + MixForTypes<float, float, kPlanar, kPlanar>(source); } else { NOTREACHED(); }
diff --git a/src/media/base/shell_audio_bus.h b/src/media/base/shell_audio_bus.h index 6c43f8c..5beaf2c 100644 --- a/src/media/base/shell_audio_bus.h +++ b/src/media/base/shell_audio_bus.h
@@ -59,6 +59,8 @@ size_t channels() const { return channels_; } size_t frames() const { return frames_; } + SampleType sample_type() const { return sample_type_; } + StorageType storage_type() const { return storage_type_; } size_t GetSampleSizeInBytes() const; const uint8* interleaved_data() const; const uint8* planar_data(size_t channel) const; @@ -101,6 +103,40 @@ void Mix(const ShellAudioBus& source); void Mix(const ShellAudioBus& source, const std::vector<float>& matrix); + public: + // The .*ForTypes? functions below are optimized versions that assume what + // storage type the bus is using. They are meant to be called after + // checking what storage type the bus is once, and then performing a batch + // of operations, where it is known that the type will not change. + template <typename SampleTypeName, StorageType T> + inline uint8* GetSamplePtrForType(size_t channel, size_t frame) const { + DCHECK_LT(channel, channels_); + DCHECK_LT(frame, frames_); + + if (T == kInterleaved) { + return channel_data_[0] + + sizeof(SampleTypeName) * (channels_ * frame + channel); + } else if (T == kPlanar) { + return channel_data_[channel] + sizeof(SampleTypeName) * frame; + } else { + NOTREACHED(); + } + + return NULL; + } + + template <typename SampleTypeName, StorageType T> + inline SampleTypeName GetSampleForType(size_t channel, size_t frame) const { + return *reinterpret_cast<const SampleTypeName*>( + GetSamplePtrForType<SampleTypeName, T>(channel, frame)); + } + + template <typename SourceSampleType, + typename DestSampleType, + StorageType SourceStorageType, + StorageType DestStorageType> + void MixForTypes(const ShellAudioBus& source); + private: void SetFloat32Sample(size_t channel, size_t frame, float sample) { DCHECK_EQ(sample_type_, kFloat32); @@ -109,20 +145,6 @@ uint8* GetSamplePtr(size_t channel, size_t frame); const uint8* GetSamplePtr(size_t channel, size_t frame) const; - // The *ForType functions below are optimized versions that assume what - // storage type the bus is using. They are meant to be called after checking - // what storage type the bus is once, and then performing a batch of - // operations, where it is known that the type will not change. - // Note: The bus must have storage type of kFloat32. - template <StorageType T> - inline uint8* GetSamplePtrForType(size_t channel, size_t frame) const; - - template <StorageType T> - inline float GetFloat32SampleForType(size_t channel, size_t frame) const; - - template <StorageType SourceStorageType, StorageType DestStorageType> - void MixForType(const ShellAudioBus& source); - // Contiguous block of channel memory if the memory is owned by this object. scoped_ptr_malloc<uint8, base::ScopedPtrAlignedFree> data_;
diff --git a/src/media/base/starboard_player.cc b/src/media/base/starboard_player.cc index 2708468..b62db8a 100644 --- a/src/media/base/starboard_player.cc +++ b/src/media/base/starboard_player.cc
@@ -290,11 +290,15 @@ SbMediaVideoCodec video_codec = MediaVideoCodecToSbMediaVideoCodec(video_config_.codec()); - player_ = SbPlayerCreate(window_, video_codec, audio_codec, - SB_PLAYER_NO_DURATION, drm_system_, &audio_header, - &StarboardPlayer::DeallocateSampleCB, - &StarboardPlayer::DecoderStatusCB, - &StarboardPlayer::PlayerStatusCB, this); + player_ = SbPlayerCreate( + window_, video_codec, audio_codec, SB_PLAYER_NO_DURATION, drm_system_, + &audio_header, &StarboardPlayer::DeallocateSampleCB, + &StarboardPlayer::DecoderStatusCB, &StarboardPlayer::PlayerStatusCB, this +#if SB_VERSION(3) + , + NULL // provider +#endif + ); set_bounds_helper_->SetPlayer(this); }
diff --git a/src/media/filters/source_buffer_stream.cc b/src/media/filters/source_buffer_stream.cc index 7f3678d..25bcfe2 100644 --- a/src/media/filters/source_buffer_stream.cc +++ b/src/media/filters/source_buffer_stream.cc
@@ -1682,7 +1682,7 @@ } base::TimeDelta SourceBufferRange::GetStartTimestamp() const { - DCHECK(!buffers_.empty()); + DLOG_IF(WARNING, buffers_.empty()) << "|buffers_| cannot be empty."; base::TimeDelta start_timestamp = media_segment_start_time_; if (start_timestamp == kNoTimestamp()) start_timestamp = buffers_.front()->GetDecodeTimestamp();
diff --git a/src/media/media.gyp b/src/media/media.gyp index 12ebe12..4f6d3d0 100644 --- a/src/media/media.gyp +++ b/src/media/media.gyp
@@ -431,6 +431,14 @@ 'dependencies': [ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', 'shared_memory_support', + ], + }], + ['OS != "ios" and cobalt != 1', { + 'dependencies': [ + # This is only used in [x11|gl|skcanvas]_video_renderer.cc, but not + # when compiling Cobalt. When linking as a shared library, the + # unused optimized YUV functions are left as undefined symbols + # after they get dead-stripped. 'yuv_convert', ], }],
diff --git a/src/nb/allocator.h b/src/nb/allocator.h index c5ddf7c..75ac4ed 100644 --- a/src/nb/allocator.h +++ b/src/nb/allocator.h
@@ -42,14 +42,21 @@ // Will return NULL if the allocation fails. virtual void* Allocate(std::size_t size, std::size_t alignment) = 0; - // Allocates a range of memory of the given size for the given alignment. - // Returns a pointer that may not be aligned but points to a memory area that - // is still large enough when the pointer is subsequently aligned to the given - // alignment. It can allocate up to size + alignment - 1 bytes. This allows a - // reuse allocator to use the padding area as a free block. Will return NULL - // if the allocation fails. - virtual void* AllocateForAlignment(std::size_t size, - std::size_t alignment) = 0; + // When supported, allocates a range of memory of the given size for the given + // alignment. Returns a pointer that may not be aligned but points to a memory + // area that is still large enough when the pointer is subsequently aligned to + // the given alignment. This allows a reuse allocator to use the padding area + // as a free block. |size| will be set to the actual size allocated on + // successful allocations. Will return NULL if the allocation fails. In the + // case that the underlying block size is inconvenient or impossible to be + // retreived, the |size| can remain unchanged for a successful allocation. The + // user may lose the ability to combine two adjacent allocations in this case. + // Note that the coding style recommends that in/out parameters to be placed + // after input parameters but |size| is kept in the left for consistency. + virtual void* AllocateForAlignment(std::size_t* /*size*/, + std::size_t /*alignment*/) { + return 0; + } // Frees memory previously allocated via any call to Allocate(). virtual void Free(void* memory) = 0;
diff --git a/src/nb/allocator_decorator.cc b/src/nb/allocator_decorator.cc index daa5320..18578b1 100644 --- a/src/nb/allocator_decorator.cc +++ b/src/nb/allocator_decorator.cc
@@ -68,7 +68,7 @@ return impl_->Allocate(size, alignment); } -void* AllocatorDecorator::AllocateForAlignment(std::size_t size, +void* AllocatorDecorator::AllocateForAlignment(std::size_t* size, std::size_t alignment) { ScopedLock scoped_lock(lock_); return impl_->AllocateForAlignment(size, alignment);
diff --git a/src/nb/allocator_decorator.h b/src/nb/allocator_decorator.h index 13969ad..78a8407 100644 --- a/src/nb/allocator_decorator.h +++ b/src/nb/allocator_decorator.h
@@ -34,7 +34,7 @@ void* Allocate(std::size_t size); void* Allocate(std::size_t size, std::size_t alignment); - void* AllocateForAlignment(std::size_t size, std::size_t alignment); + void* AllocateForAlignment(std::size_t* size, std::size_t alignment); void Free(void* memory); std::size_t GetCapacity() const; std::size_t GetAllocated() const;
diff --git a/src/nb/analytics/memory_tracker.cc b/src/nb/analytics/memory_tracker.cc new file mode 100644 index 0000000..56471dd --- /dev/null +++ b/src/nb/analytics/memory_tracker.cc
@@ -0,0 +1,67 @@ +/* + * Copyright 2016 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 "nb/analytics/memory_tracker.h" + +#include "nb/analytics/memory_tracker_impl.h" +#include "nb/scoped_ptr.h" +#include "starboard/once.h" + +namespace nb { +namespace analytics { +namespace { +SB_ONCE_INITIALIZE_FUNCTION(MemoryTrackerImpl, GetMemoryTrackerImplSingleton); +} // namespace + +MemoryTracker* MemoryTracker::Get() { + MemoryTracker* t = GetMemoryTrackerImplSingleton(); + return t; +} + + +MemoryStats GetProcessMemoryStats() { + MemoryStats memory_stats; + + memory_stats.total_cpu_memory = SbSystemGetTotalCPUMemory(); + memory_stats.used_cpu_memory = SbSystemGetUsedCPUMemory(); + + if (SbSystemHasCapability(kSbSystemCapabilityCanQueryGPUMemoryStats)) { + int64_t used_gpu_memory = SbSystemGetUsedGPUMemory(); + memory_stats.total_gpu_memory = SbSystemGetTotalGPUMemory(); + memory_stats.used_gpu_memory = SbSystemGetUsedGPUMemory(); + } + return memory_stats; +} + +nb::scoped_ptr<MemoryTrackerPrintThread> +CreateDebugPrintThread(MemoryTracker* memory_tracker) { + return nb::scoped_ptr<MemoryTrackerPrintThread>( + new MemoryTrackerPrintThread(memory_tracker)); +} + +nb::scoped_ptr<MemoryTrackerPrintCSVThread> +CreateDebugPrintCSVThread(MemoryTracker* memory_tracker, + int sample_interval_ms, + int total_sampling_time_ms) { + return nb::scoped_ptr<MemoryTrackerPrintCSVThread>( + new MemoryTrackerPrintCSVThread( + memory_tracker, + sample_interval_ms, + total_sampling_time_ms)); +} + +} // namespace analytics +} // namespace nb
diff --git a/src/nb/analytics/memory_tracker.h b/src/nb/analytics/memory_tracker.h new file mode 100644 index 0000000..7d1c332 --- /dev/null +++ b/src/nb/analytics/memory_tracker.h
@@ -0,0 +1,169 @@ +/* + * Copyright 2016 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 NB_MEMORY_TRACKER_H_ +#define NB_MEMORY_TRACKER_H_ + +#include <vector> +#include "starboard/configuration.h" +#include "starboard/types.h" +#include "nb/scoped_ptr.h" + +namespace nb { + +namespace analytics { + +class MemoryTracker; +class MemoryTrackerPrintThread; +class MemoryTrackerPrintCSVThread; +class AllocationVisitor; +class AllocationGroup; +class AllocationRecord; + +struct MemoryStats { + MemoryStats() : total_cpu_memory(0), used_cpu_memory(0), + total_gpu_memory(0), used_gpu_memory(0) {} + int64_t total_cpu_memory; + int64_t used_cpu_memory; + int64_t total_gpu_memory; + int64_t used_gpu_memory; +}; + +MemoryStats GetProcessMemoryStats(); + +// Creates a MemoryTracker instance that implements the +// MemoryTracker. Once the instance is created it can begin tracking +// system allocations by calling InstallGlobalTrackingHooks(). +// Deleting the MemoryTracker is forbidden. +// +// Example, Creation and Hooking: +// static MemoryTracker* s_global_tracker = +// GetOrCreateMemoryTracker(); +// s_global_tracker->InstallGlobalTrackingHooks(); // now tracking memory. +// +// Data about the allocations are aggregated under AllocationGroups and it's +// recommended that GetAllocationGroups(...) is used to get simple allocation +// statistics. +// +// Deeper analytics are possible by creating an AllocationVisitor subclass and +// traversing through the internal allocations of the tracker. In this way all +// known information about allocation state of the program is made accessible. +// The visitor does not need to perform any locking as this is guaranteed by +// the MemoryTracker. +// +// Example (AllocationVisitor): +// MyAllocation visitor = ...; +// s_global_tracker->Accept(&visitor); +// visitor.PrintAllocations(); +// +// Performance: +// 1) Gold builds disallow memory tracking and therefore have zero-cost +// for this feature. +// 2) All other builds that allow memory tracking have minimal cost as long +// as memory tracking has not been activated. This is facilitated by NOT +// using locks, at the expense of thread safety during teardown (hence the +// reason why you should NOT delete a memory tracker with hooks installed). +// 3) When the memory tracking has been activated then there is a non-trivial +// performance cost in terms of CPU and memory for the feature. +class MemoryTracker { + public: + // Gets the singleton instance of the default MemoryTracker. This + // is created the first time it is used. + static MemoryTracker* Get(); + + MemoryTracker() {} + virtual bool InstallGlobalTrackingHooks() = 0; + + // It's recommended the MemoryTracker is never removed or deleted during the + // runtime. + virtual void RemoveGlobalTrackingHooks() = 0; + + // Returns the total amount of bytes that are tracked. + virtual int64_t GetTotalAllocationBytes() = 0; + virtual int64_t GetTotalNumberOfAllocations() = 0; + + // Allows probing of all memory allocations. The visitor does not need to + // perform any locking and can allocate memory during it's operation. + virtual void Accept(AllocationVisitor* visitor) = 0; + + // Collects all memory groups that exist. The AllocationGroups lifetime + // exists for as long as the MemoryTracker instance is alive. + virtual void GetAllocationGroups( + std::vector<const AllocationGroup*>* output) = 0; + + // Enabled/disables memory tracking in the current thread. + virtual void SetMemoryTrackingEnabled(bool on) = 0; + // Returns the memory tracking state in the current thread. + virtual bool IsMemoryTrackingEnabled() const = 0; + + // Returns true if the memory was successfully tracked. + virtual bool AddMemoryTracking(const void* memory, size_t size) = 0; + // Returns a non-zero size if the memory was successfully removed. + virtual size_t RemoveMemoryTracking(const void* memory) = 0; + // Returns true if the memory has tracking. When true is returned then the + // supplied AllocRecord is written. + virtual bool GetMemoryTracking(const void* memory, + AllocationRecord* record) const = 0; + + protected: + virtual ~MemoryTracker() {} + + SB_DISALLOW_COPY_AND_ASSIGN(MemoryTracker); +}; + +// A visitor class which is useful for inspecting data. +class AllocationVisitor { + public: + // Returns true to keep visiting, otherwise abort. + virtual bool Visit(const void* memory, + const AllocationRecord& alloc_record) = 0; + virtual ~AllocationVisitor() {} +}; + +// Contains an allocation record for a pointer including it's size and what +// AllocationGroup it was constructed under. +class AllocationRecord { + public: + AllocationRecord() : size(0), allocation_group(NULL) {} + AllocationRecord(size_t sz, AllocationGroup* group) + : size(sz), allocation_group(group) {} + + static AllocationRecord Empty() { return AllocationRecord(); } + bool IsEmpty() const { return !size && !allocation_group; } + size_t size; + AllocationGroup* allocation_group; +}; + +// Creates a SimpleThread that will output the state of the memory +// periodically. Start()/Cancel()/Join() are called AUTOMATICALLY with +// this object. Start() is on the returned thread before it is returned. +// Join() is automatically called on destruction. +scoped_ptr<MemoryTrackerPrintThread> + CreateDebugPrintThread(MemoryTracker* memory_tracker); + +// Creates a SimpleThread that will output the state of the memory +// periodically. Start()/Cancel()/Join() are called AUTOMATICALLY with +// this object. Start() is on the returned thread before it is returned. +// Join() is automatically called on destruction. +scoped_ptr<MemoryTrackerPrintCSVThread> + CreateDebugPrintCSVThread(MemoryTracker* memory_tracker, + int sample_interval_ms, + int total_sampling_time_ms); + +} // namespace analytics +} // namespace nb + +#endif // NB_MEMORY_TRACKER_H_
diff --git a/src/nb/analytics/memory_tracker_helpers.cc b/src/nb/analytics/memory_tracker_helpers.cc new file mode 100644 index 0000000..a1eedc8 --- /dev/null +++ b/src/nb/analytics/memory_tracker_helpers.cc
@@ -0,0 +1,280 @@ +/* + * Copyright 2016 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 "nb/analytics/memory_tracker_helpers.h" + +#include <stdint.h> +#include <vector> + +#include "nb/hash.h" +#include "starboard/configuration.h" +#include "starboard/log.h" + +namespace nb { +namespace analytics { + +AllocationGroup::AllocationGroup(const std::string& name) + : name_(name), allocation_bytes_(0), num_allocations_(0) { +} + +AllocationGroup::~AllocationGroup() {} + +void AllocationGroup::AddAllocation(int64_t num_bytes) { + if (num_bytes == 0) + return; + int num_alloc_diff = num_bytes > 0 ? 1 : -1; + allocation_bytes_.fetch_add(num_bytes); + num_allocations_.fetch_add(num_alloc_diff); +} + +void AllocationGroup::GetAggregateStats(int32_t* num_allocs, + int64_t* allocation_bytes) const { + *num_allocs = num_allocations_.load(); + *allocation_bytes = allocation_bytes_.load(); +} + +int64_t AllocationGroup::allocation_bytes() const { + return allocation_bytes_.load(); +} + +int32_t AllocationGroup::num_allocations() const { + return num_allocations_.load(); +} + +AtomicStringAllocationGroupMap::AtomicStringAllocationGroupMap() { + unaccounted_group_ = Ensure("Unaccounted"); +} + +AtomicStringAllocationGroupMap::~AtomicStringAllocationGroupMap() { + unaccounted_group_ = NULL; + while (!group_map_.empty()) { + Map::iterator it = group_map_.begin(); + delete it->second; + group_map_.erase(it); + } +} + +AllocationGroup* AtomicStringAllocationGroupMap::Ensure( + const std::string& name) { + starboard::ScopedLock lock(mutex_); + Map::const_iterator found_it = group_map_.find(name); + if (found_it != group_map_.end()) { + return found_it->second; + } + AllocationGroup* group = new AllocationGroup(name); + group_map_[name] = group; + return group; +} + +AllocationGroup* AtomicStringAllocationGroupMap::GetDefaultUnaccounted() { + return unaccounted_group_; +} + +bool AtomicStringAllocationGroupMap::Erase(const std::string& name) { + starboard::ScopedLock lock(mutex_); + Map::iterator found_it = group_map_.find(name); + if (found_it == group_map_.end()) { + // Didn't find it. + return false; + } + group_map_.erase(found_it); + return true; +} + +void AtomicStringAllocationGroupMap::GetAll( + std::vector<const AllocationGroup*>* output) const { + starboard::ScopedLock lock(mutex_); + for (Map::const_iterator it = group_map_.begin(); it != group_map_.end(); + ++it) { + output->push_back(it->second); + } +} + +void AllocationGroupStack::Push(AllocationGroup* group) { + alloc_group_stack_.push_back(group); +} + +void AllocationGroupStack::Pop() { + alloc_group_stack_.pop_back(); +} + +AllocationGroup* AllocationGroupStack::Peek() { + if (alloc_group_stack_.empty()) { + return NULL; + } + return alloc_group_stack_.back(); +} + +AtomicAllocationMap::AtomicAllocationMap() {} + +AtomicAllocationMap::~AtomicAllocationMap() {} + +bool AtomicAllocationMap::Add(const void* memory, + const AllocationRecord& alloc_record) { + starboard::ScopedLock lock(mutex_); + const bool inserted = + pointer_map_.insert(std::make_pair(memory, alloc_record)).second; + return inserted; +} + +bool AtomicAllocationMap::Get(const void* memory, + AllocationRecord* alloc_record) const { + starboard::ScopedLock lock(mutex_); + PointerMap::const_iterator found_it = pointer_map_.find(memory); + if (found_it == pointer_map_.end()) { + if (alloc_record) { + *alloc_record = AllocationRecord::Empty(); + } + return false; + } + if (alloc_record) { + *alloc_record = found_it->second; + } + return true; +} + +bool AtomicAllocationMap::Remove(const void* memory, + AllocationRecord* alloc_record) { + starboard::ScopedLock lock(mutex_); + PointerMap::iterator found_it = pointer_map_.find(memory); + + if (found_it == pointer_map_.end()) { + if (alloc_record) { + *alloc_record = AllocationRecord::Empty(); + } + return false; + } + if (alloc_record) { + *alloc_record = found_it->second; + } + pointer_map_.erase(found_it); + return true; +} + +bool AtomicAllocationMap::Accept(AllocationVisitor* visitor) const { + starboard::ScopedLock lock(mutex_); + for (PointerMap::const_iterator it = pointer_map_.begin(); + it != pointer_map_.end(); ++it) { + const void* memory = it->first; + const AllocationRecord& alloc_rec = it->second; + if (!visitor->Visit(memory, alloc_rec)) { + return false; + } + } + return true; +} + +size_t AtomicAllocationMap::Size() const { + starboard::ScopedLock lock(mutex_); + return pointer_map_.size(); +} + +bool AtomicAllocationMap::Empty() const { + starboard::ScopedLock lock(mutex_); + return pointer_map_.empty(); +} + +void AtomicAllocationMap::Clear() { + starboard::ScopedLock lock(mutex_); + for (PointerMap::iterator it = pointer_map_.begin(); + it != pointer_map_.end(); ++it) { + const AllocationRecord& rec = it->second; + AllocationGroup* group = rec.allocation_group; + group->AddAllocation(-rec.size); + } + return pointer_map_.clear(); +} + +ConcurrentAllocationMap::ConcurrentAllocationMap() : pointer_map_array_() {} + +ConcurrentAllocationMap::~ConcurrentAllocationMap() { + Clear(); +} + +bool ConcurrentAllocationMap::Add(const void* memory, + const AllocationRecord& alloc_record) { + AtomicAllocationMap& map = GetMapForPointer(memory); + return map.Add(memory, alloc_record); +} + +bool ConcurrentAllocationMap::Get(const void* memory, + AllocationRecord* alloc_record) const { + const AtomicAllocationMap& map = GetMapForPointer(memory); + return map.Get(memory, alloc_record); +} + +bool ConcurrentAllocationMap::Remove(const void* memory, + AllocationRecord* alloc_record) { + AtomicAllocationMap& map = GetMapForPointer(memory); + bool output = map.Remove(memory, alloc_record); + return output; +} + +size_t ConcurrentAllocationMap::Size() const { + size_t size = 0; + for (int i = 0; i < kNumElements; ++i) { + const AtomicAllocationMap& map = pointer_map_array_[i]; + size += map.Size(); + } + return size; +} + +bool ConcurrentAllocationMap::Empty() const { + return 0 == Size(); +} + +void ConcurrentAllocationMap::Clear() { + for (int i = 0; i < kNumElements; ++i) { + AtomicAllocationMap& map = pointer_map_array_[i]; + map.Clear(); + } +} + +bool ConcurrentAllocationMap::Accept(AllocationVisitor* visitor) const { + for (int i = 0; i < kNumElements; ++i) { + const AtomicAllocationMap& map = pointer_map_array_[i]; + if (!map.Accept(visitor)) { + return false; // Early out. + } + } + return true; +} + +size_t ConcurrentAllocationMap::hash_ptr(const void* ptr) { + uintptr_t val = reinterpret_cast<uintptr_t>(ptr); + + return RuntimeHash32(reinterpret_cast<const char*>(&val), sizeof(val)); +} + +int ConcurrentAllocationMap::ToIndex(const void* ptr) const { + SB_DCHECK(0 != kNumElements); + uint32_t hash_val = hash_ptr(ptr); + int index = hash_val % kNumElements; + return index; +} + +AtomicAllocationMap& ConcurrentAllocationMap::GetMapForPointer( + const void* ptr) { + return pointer_map_array_[ToIndex(ptr)]; +} + +const AtomicAllocationMap& ConcurrentAllocationMap::GetMapForPointer( + const void* ptr) const { + return pointer_map_array_[ToIndex(ptr)]; +} + +} // namespace analytics +} // namespace nb
diff --git a/src/nb/analytics/memory_tracker_helpers.h b/src/nb/analytics/memory_tracker_helpers.h new file mode 100644 index 0000000..d004620 --- /dev/null +++ b/src/nb/analytics/memory_tracker_helpers.h
@@ -0,0 +1,233 @@ +/* + * Copyright 2016 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 NB_MEMORY_TRACKER_HELPERS_H_ +#define NB_MEMORY_TRACKER_HELPERS_H_ + +#include <map> +#include <vector> + +#include "nb/analytics/memory_tracker.h" +#include "nb/atomic.h" +#include "nb/simple_thread.h" +#include "starboard/mutex.h" +#include "starboard/thread.h" +#include "starboard/types.h" +#include "starboard/log.h" + +namespace nb { +namespace analytics { + +class AllocationGroup; +class AllocationRecord; + +template <typename Type> +class ThreadLocalPointer { + public: + ThreadLocalPointer() { + slot_ = SbThreadCreateLocalKey(NULL); // No destructor for pointer. + SB_DCHECK(kSbThreadLocalKeyInvalid != slot_); + } + + ~ThreadLocalPointer() { SbThreadDestroyLocalKey(slot_); } + + Type* Get() const { + void* ptr = SbThreadGetLocalValue(slot_); + Type* type_ptr = static_cast<Type*>(ptr); + return type_ptr; + } + + void Set(Type* ptr) { + void* void_ptr = static_cast<void*>(ptr); + SbThreadSetLocalValue(slot_, void_ptr); + } + + private: + SbThreadLocalKey slot_; + SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalPointer<Type>); +}; + +class ThreadLocalBoolean { + public: + ThreadLocalBoolean() : default_value_(false) {} + explicit ThreadLocalBoolean(bool default_value) + : default_value_(default_value) {} + ~ThreadLocalBoolean() {} + + bool Get() const { + bool val = tlp_.Get() != NULL; + return val ^ default_value_; + } + + void Set(bool val) { + val = val ^ default_value_; + tlp_.Set(val ? TruePointer() : FalsePointer()); + } + + private: + static void* TruePointer() { return reinterpret_cast<void*>(0x1); } + static void* FalsePointer() { return NULL; } + ThreadLocalPointer<void> tlp_; + const bool default_value_; + + SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalBoolean); +}; + +// An AllocationGroup is a collection of allocations that are logically lumped +// together, such as "Javascript" or "Graphics". +class AllocationGroup { + public: + explicit AllocationGroup(const std::string& name); + ~AllocationGroup(); + const std::string& name() const { return name_; } + + void AddAllocation(int64_t num_bytes); + void GetAggregateStats(int32_t* num_allocs, int64_t* allocation_bytes) const; + + int64_t allocation_bytes() const; + int32_t num_allocations() const; + + private: + const std::string name_; + nb::atomic_int64_t allocation_bytes_; + nb::atomic_int32_t num_allocations_; + + SB_DISALLOW_COPY_AND_ASSIGN(AllocationGroup); +}; + +// A self locking data structure that maps strings -> AllocationGroups. This is +// used to resolve MemoryGroup names (e.g. "Javascript") to an AllocationGroup +// which can be used to group allocations together. +class AtomicStringAllocationGroupMap { + public: + AtomicStringAllocationGroupMap(); + ~AtomicStringAllocationGroupMap(); + + AllocationGroup* Ensure(const std::string& name); + AllocationGroup* GetDefaultUnaccounted(); + + bool Erase(const std::string& key); + void GetAll(std::vector<const AllocationGroup*>* output) const; + + private: + typedef std::map<std::string, AllocationGroup*> Map; + Map group_map_; + AllocationGroup* unaccounted_group_; + mutable starboard::Mutex mutex_; + + SB_DISALLOW_COPY_AND_ASSIGN(AtomicStringAllocationGroupMap); +}; + +class AllocationGroupStack { + public: + AllocationGroupStack() { Push_DebugBreak(NULL); } + ~AllocationGroupStack() {} + + void Push(AllocationGroup* group); + void Pop(); + AllocationGroup* Peek(); + + void Push_DebugBreak(AllocationGroup* ag) { debug_stack_.push_back(ag); } + void Pop_DebugBreak() { debug_stack_.pop_back(); } + AllocationGroup* Peek_DebugBreak() { + if (debug_stack_.empty()) { + return NULL; + } + return debug_stack_.back(); + } + + private: + SB_DISALLOW_COPY_AND_ASSIGN(AllocationGroupStack); + typedef std::vector<AllocationGroup*> AllocationGroupPtrVec; + AllocationGroupPtrVec alloc_group_stack_, debug_stack_; +}; + +// A per-pointer map of allocations to AllocRecords. This map is thread safe. +class AtomicAllocationMap { + public: + AtomicAllocationMap(); + ~AtomicAllocationMap(); + + // Returns true if Added. Otherwise false means that the pointer + // already existed. + bool Add(const void* memory, const AllocationRecord& alloc_record); + + // Returns true if the memory exists in this set. + bool Get(const void* memory, AllocationRecord* alloc_record) const; + + // Return true if the memory existed in this set. If true + // then output alloc_record is written with record that was found. + // otherwise the record is written as 0 bytes and null key. + bool Remove(const void* memory, AllocationRecord* alloc_record); + + bool Accept(AllocationVisitor* visitor) const; + + size_t Size() const; + bool Empty() const; + void Clear(); + + private: + SB_DISALLOW_COPY_AND_ASSIGN(AtomicAllocationMap); + typedef std::map<const void*, AllocationRecord> PointerMap; + + PointerMap pointer_map_; + mutable starboard::Mutex mutex_; +}; + +// A per-pointer map of allocations to AllocRecords. This is a hybrid data +// structure consisting of a hashtable of maps. Each pointer that is +// stored or retrieved is hashed to a random bucket. Each bucket has it's own +// lock. This distributed pattern increases performance significantly by +// reducing contention. The top-level hashtable is of constant size and does +// not resize. Each bucket is implemented as it's own map of elements. +class ConcurrentAllocationMap { + public: + static const int kNumElements = 511; + ConcurrentAllocationMap(); + ~ConcurrentAllocationMap(); + + // Returns true if Added. Otherwise false means that the pointer + // already existed. + bool Add(const void* memory, const AllocationRecord& alloc_record); + // Returns true if the memory exists in this set. + bool Get(const void* memory, AllocationRecord* alloc_record) const; + // Return true if the memory existed in this set. If true + // then output alloc_record is written with record that was found. + // otherwise the record is written as 0 bytes and null key. + bool Remove(const void* memory, AllocationRecord* alloc_record); + size_t Size() const; + bool Empty() const; + void Clear(); + + // Provides access to all the allocations within in a thread safe manner. + bool Accept(AllocationVisitor* visitor) const; + + AtomicAllocationMap& GetMapForPointer(const void* ptr); + const AtomicAllocationMap& GetMapForPointer(const void* ptr) const; + + private: + SB_DISALLOW_COPY_AND_ASSIGN(ConcurrentAllocationMap); + // Takes a pointer and generates a hash. + static size_t hash_ptr(const void* ptr); + + int ToIndex(const void* ptr) const; + AtomicAllocationMap pointer_map_array_[kNumElements]; +}; + +} // namespace analytics +} // namespace nb + +#endif // NB_MEMORY_TRACKER_HELPERS_H_
diff --git a/src/nb/analytics/memory_tracker_helpers_test.cc b/src/nb/analytics/memory_tracker_helpers_test.cc new file mode 100644 index 0000000..ebef340 --- /dev/null +++ b/src/nb/analytics/memory_tracker_helpers_test.cc
@@ -0,0 +1,124 @@ +/* + * Copyright 2016 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 "nb/analytics/memory_tracker_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace nb { +namespace analytics { +namespace { + +/////////////////////////////////////////////////////////////////////////////// +TEST(AtomicStringAllocationGroupMap, Use) { + AtomicStringAllocationGroupMap map; + AllocationGroup* tag = map.Ensure("MemoryRegion"); + EXPECT_TRUE(tag != NULL); + EXPECT_EQ(std::string("MemoryRegion"), tag->name()); + AllocationGroup* tag2 = map.Ensure("MemoryRegion"); + EXPECT_EQ(tag, tag2); +} + +TEST(AtomicAllocationMap, AddHasRemove) { + AtomicAllocationMap atomic_pointer_map; + int32_t int_a = 0; + int32_t int_b = 1; + + // Initially empty. + EXPECT_TRUE(atomic_pointer_map.Empty()); + + const AllocationRecord int32_alloc_record = + AllocationRecord(sizeof(int32_t), NULL); + AllocationRecord alloc_output; + + EXPECT_TRUE(atomic_pointer_map.Add(&int_a, int32_alloc_record)); + EXPECT_FALSE(atomic_pointer_map.Add(&int_a, int32_alloc_record)); + EXPECT_TRUE(atomic_pointer_map.Get(&int_a, &alloc_output)); + EXPECT_EQ(alloc_output.size, sizeof(int32_t)); + + EXPECT_FALSE(atomic_pointer_map.Get(&int_b, &alloc_output)); + EXPECT_EQ(0, alloc_output.size); + // Adding pointer to int_a increases set to 1 element. + EXPECT_EQ(atomic_pointer_map.Size(), 1); + EXPECT_FALSE(atomic_pointer_map.Empty()); + + // Adds pointer to int_b. + EXPECT_TRUE(atomic_pointer_map.Add(&int_b, int32_alloc_record)); + EXPECT_TRUE(atomic_pointer_map.Get(&int_b, &alloc_output)); + EXPECT_EQ(sizeof(int32_t), alloc_output.size); + // Expect that the second pointer added will increase the number of elements. + EXPECT_EQ(atomic_pointer_map.Size(), 2); + EXPECT_FALSE(atomic_pointer_map.Empty()); + + // Now remove the elements and ensure that they no longer found and that + // the size of the table shrinks to empty. + EXPECT_TRUE(atomic_pointer_map.Remove(&int_a, &alloc_output)); + EXPECT_EQ(sizeof(int32_t), alloc_output.size); + EXPECT_EQ(atomic_pointer_map.Size(), 1); + EXPECT_FALSE(atomic_pointer_map.Remove(&int_a, &alloc_output)); + EXPECT_EQ(0, alloc_output.size); + EXPECT_TRUE(atomic_pointer_map.Remove(&int_b, &alloc_output)); + EXPECT_EQ(atomic_pointer_map.Size(), 0); + + EXPECT_TRUE(atomic_pointer_map.Empty()); +} + +TEST(ConcurrentAllocationMap, AddHasRemove) { + ConcurrentAllocationMap alloc_map; + int32_t int_a = 0; + int32_t int_b = 1; + + // Initially empty. + EXPECT_TRUE(alloc_map.Empty()); + + const AllocationRecord int32_alloc_record = + AllocationRecord(sizeof(int32_t), NULL); + AllocationRecord alloc_output; + + EXPECT_TRUE(alloc_map.Add(&int_a, int32_alloc_record)); + EXPECT_FALSE(alloc_map.Add(&int_a, int32_alloc_record)); + EXPECT_TRUE(alloc_map.Get(&int_a, &alloc_output)); + EXPECT_EQ(alloc_output.size, sizeof(int32_t)); + + EXPECT_FALSE(alloc_map.Get(&int_b, &alloc_output)); + EXPECT_EQ(0, alloc_output.size); + // Adding pointer to int_a increases set to 1 element. + EXPECT_EQ(alloc_map.Size(), 1); + EXPECT_FALSE(alloc_map.Empty()); + + // Adds pointer to int_b. + EXPECT_TRUE(alloc_map.Add(&int_b, int32_alloc_record)); + EXPECT_TRUE(alloc_map.Get(&int_b, &alloc_output)); + EXPECT_EQ(sizeof(int32_t), alloc_output.size); + // Expect that the second pointer added will increase the number of elements. + EXPECT_EQ(alloc_map.Size(), 2); + EXPECT_FALSE(alloc_map.Empty()); + + // Now remove the elements and ensure that they no longer found and that + // the size of the table shrinks to empty. + EXPECT_TRUE(alloc_map.Remove(&int_a, &alloc_output)); + EXPECT_EQ(sizeof(int32_t), alloc_output.size); + EXPECT_EQ(alloc_map.Size(), 1); + EXPECT_FALSE(alloc_map.Remove(&int_a, &alloc_output)); + EXPECT_EQ(0, alloc_output.size); + EXPECT_TRUE(alloc_map.Remove(&int_b, &alloc_output)); + EXPECT_EQ(alloc_map.Size(), 0); + + EXPECT_TRUE(alloc_map.Empty()); +} + +} // namespace +} // namespace analytics +} // namespace nb
diff --git a/src/nb/analytics/memory_tracker_impl.cc b/src/nb/analytics/memory_tracker_impl.cc new file mode 100644 index 0000000..a4a8c89 --- /dev/null +++ b/src/nb/analytics/memory_tracker_impl.cc
@@ -0,0 +1,836 @@ +/* + * Copyright 2016 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 "nb/analytics/memory_tracker_impl.h" + +#include <cstring> +#include <iomanip> +#include <iterator> +#include <sstream> + +#include "nb/atomic.h" +#include "starboard/atomic.h" +#include "starboard/log.h" +#include "starboard/time.h" + +namespace nb { +namespace analytics { +namespace { +// NoMemoryTracking will disable memory tracking while in the current scope of +// execution. When the object is destroyed it will reset the previous state +// of allocation tracking. +// Example: +// void Foo() { +// NoMemoryTracking no_memory_tracking_in_scope; +// int* ptr = new int(); // ptr is not tracked. +// delete ptr; +// return; // Previous memory tracking state is restored. +// } +class NoMemoryTracking { + public: + NoMemoryTracking(MemoryTracker* owner) : owner_(owner) { + prev_val_ = owner_->IsMemoryTrackingEnabled(); + owner_->SetMemoryTrackingEnabled(false); + } + ~NoMemoryTracking() { owner_->SetMemoryTrackingEnabled(prev_val_); } + + private: + bool prev_val_; + MemoryTracker* owner_; +}; + +// This is a simple algorithm to remove the "needle" from the haystack. Note +// that this function is simple and not well optimized. +std::string RemoveString(const std::string& haystack, const char* needle) { + const size_t NOT_FOUND = std::string::npos; + + // Base case. No modification needed. + size_t pos = haystack.find(needle); + if (pos == NOT_FOUND) { + return haystack; + } + const size_t n = strlen(needle); + std::string output; + output.reserve(haystack.size()); + + // Copy string, omitting the portion containing the "needle". + std::copy(haystack.begin(), haystack.begin() + pos, + std::back_inserter(output)); + std::copy(haystack.begin() + pos + n, haystack.end(), + std::back_inserter(output)); + + // Recursively remove same needle in haystack. + return RemoveString(output, needle); +} +} // namespace + +SbMemoryReporter* MemoryTrackerImpl::GetMemoryReporter() { + return &sb_memory_tracker_; +} + +NbMemoryScopeReporter* MemoryTrackerImpl::GetMemoryScopeReporter() { + return &nb_memory_scope_reporter_; +} + +int64_t MemoryTrackerImpl::GetTotalAllocationBytes() { + return total_bytes_allocated_.load(); +} + +AllocationGroup* MemoryTrackerImpl::GetAllocationGroup(const char* name) { + DisableMemoryTrackingInScope no_tracking(this); + AllocationGroup* alloc_group = alloc_group_map_.Ensure(name); + return alloc_group; +} + +void MemoryTrackerImpl::PushAllocationGroupByName(const char* group_name) { + AllocationGroup* group = GetAllocationGroup(group_name); + PushAllocationGroup(group); +} + +void MemoryTrackerImpl::PushAllocationGroup(AllocationGroup* alloc_group) { + if (alloc_group == NULL) { + alloc_group = alloc_group_map_.GetDefaultUnaccounted(); + } + DisableMemoryTrackingInScope no_tracking(this); + allocation_group_stack_tls_.GetOrCreate()->Push(alloc_group); +} + +AllocationGroup* MemoryTrackerImpl::PeekAllocationGroup() { + DisableMemoryTrackingInScope no_tracking(this); + AllocationGroup* out = + allocation_group_stack_tls_.GetOrCreate()->Peek(); + if (out == NULL) { + out = alloc_group_map_.GetDefaultUnaccounted(); + } + return out; +} + +void MemoryTrackerImpl::PopAllocationGroup() { + DisableMemoryTrackingInScope no_tracking(this); + AllocationGroupStack* alloc_tls = allocation_group_stack_tls_.GetOrCreate(); + alloc_tls->Pop(); + AllocationGroup* group = alloc_tls->Peek(); + // We don't allow null, so if this is encountered then push the + // "default unaccounted" alloc group. + if (group == NULL) { + alloc_tls->Push(alloc_group_map_.GetDefaultUnaccounted()); + } +} + +void MemoryTrackerImpl::GetAllocationGroups( + std::vector<const AllocationGroup*>* output) { + DisableMemoryTrackingInScope no_allocation_tracking(this); + output->reserve(100); + alloc_group_map_.GetAll(output); +} + +void MemoryTrackerImpl::GetAllocationGroups( + std::map<std::string, const AllocationGroup*>* output) { + output->clear(); + DisableMemoryTrackingInScope no_tracking(this); + std::vector<const AllocationGroup*> tmp; + GetAllocationGroups(&tmp); + for (size_t i = 0; i < tmp.size(); ++i) { + output->insert(std::make_pair(tmp[i]->name(), tmp[i])); + } +} + +void MemoryTrackerImpl::Accept(AllocationVisitor* visitor) { + DisableMemoryTrackingInScope no_mem_tracking(this); + atomic_allocation_map_.Accept(visitor); +} + +void MemoryTrackerImpl::Clear() { + // Prevent clearing of the tree from triggering a re-entrant + // memory deallocation. + atomic_allocation_map_.Clear(); + total_bytes_allocated_.store(0); +} + +void MemoryTrackerImpl::Debug_PushAllocationGroupBreakPointByName( + const char* group_name) { + DisableMemoryTrackingInScope no_tracking(this); + SB_DCHECK(group_name != NULL); + AllocationGroup* group = alloc_group_map_.Ensure(group_name); + Debug_PushAllocationGroupBreakPoint(group); +} + +void MemoryTrackerImpl::Debug_PushAllocationGroupBreakPoint( + AllocationGroup* alloc_group) { + DisableMemoryTrackingInScope no_tracking(this); + allocation_group_stack_tls_.GetOrCreate()->Push_DebugBreak(alloc_group); +} + +void MemoryTrackerImpl::Debug_PopAllocationGroupBreakPoint() { + DisableMemoryTrackingInScope no_tracking(this); + allocation_group_stack_tls_.GetOrCreate()->Pop_DebugBreak(); +} + +// Converts "2345.54" => "2,345.54". +std::string InsertCommasIntoNumberString(const std::string& input) { + typedef std::vector<char> CharVector; + typedef CharVector::iterator CharIt; + + CharVector chars(input.begin(), input.end()); + std::reverse(chars.begin(), chars.end()); + + CharIt curr_it = chars.begin(); + CharIt mid = std::find(chars.begin(), chars.end(), '.'); + if (mid == chars.end()) { + mid = curr_it; + } + + CharVector out(curr_it, mid); + + int counter = 0; + for (CharIt it = mid; it != chars.end(); ++it) { + if (counter != 0 && (counter % 3 == 0)) { + out.push_back(','); + } + if (*it != '.') { + counter++; + } + out.push_back(*it); + } + + std::reverse(out.begin(), out.end()); + std::stringstream ss; + for (int i = 0; i < out.size(); ++i) { + ss << out[i]; + } + return ss.str(); +} + +template <typename T> +std::string NumberFormatWithCommas(T val) { + // Convert value to string. + std::stringstream ss; + ss << val; + std::string s = InsertCommasIntoNumberString(ss.str()); + return s; +} + +void MemoryTrackerImpl::OnMalloc(void* context, + const void* memory, + size_t size) { + MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context); + t->AddMemoryTracking(memory, size); +} + +void MemoryTrackerImpl::OnDealloc(void* context, const void* memory) { + MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context); + t->RemoveMemoryTracking(memory); +} + +void MemoryTrackerImpl::OnMapMem(void* context, + const void* memory, + size_t size) { + // We might do something more interesting with MapMemory calls later. + OnMalloc(context, memory, size); +} + +void MemoryTrackerImpl::OnUnMapMem(void* context, + const void* memory, + size_t size) { + // We might do something more interesting with UnMapMemory calls later. + OnDealloc(context, memory); +} + +void MemoryTrackerImpl::OnPushAllocationGroup( + void* context, + NbMemoryScopeInfo* memory_scope_info) { + MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context); + void** cached_handle = &(memory_scope_info->cached_handle_); + const bool allows_caching = memory_scope_info->allows_caching_; + const char* group_name = memory_scope_info->memory_scope_name_; + + AllocationGroup* group = NULL; + if (allows_caching && *cached_handle != NULL) { + group = static_cast<AllocationGroup*>(*cached_handle); + } else { + group = t->GetAllocationGroup(group_name); + if (allows_caching) { + // Flush all pending writes so that the the pointee is well formed + // by the time the pointer becomes visible to other threads. + SbAtomicMemoryBarrier(); + *cached_handle = static_cast<void*>(group); + } + } + + t->PushAllocationGroup(group); +} + +void MemoryTrackerImpl::OnPopAllocationGroup(void* context) { + MemoryTrackerImpl* t = static_cast<MemoryTrackerImpl*>(context); + t->PopAllocationGroup(); +} + +void MemoryTrackerImpl::Initialize( + SbMemoryReporter* sb_memory_reporter, + NbMemoryScopeReporter* memory_scope_reporter) { + SbMemoryReporter mem_reporter = { + MemoryTrackerImpl::OnMalloc, MemoryTrackerImpl::OnDealloc, + + MemoryTrackerImpl::OnMapMem, MemoryTrackerImpl::OnUnMapMem, + + this}; + + NbMemoryScopeReporter mem_scope_reporter = { + MemoryTrackerImpl::OnPushAllocationGroup, + MemoryTrackerImpl::OnPopAllocationGroup, + + this, + }; + + *sb_memory_reporter = mem_reporter; + *memory_scope_reporter = mem_scope_reporter; +} + +void MemoryTrackerImpl::SetThreadFilter(SbThreadId tid) { + thread_filter_id_ = tid; +} + +bool MemoryTrackerImpl::IsCurrentThreadAllowedToReport() const { + if (thread_filter_id_ == kSbThreadInvalidId) { + return true; + } + return SbThreadGetId() == thread_filter_id_; +} + +MemoryTrackerImpl::DisableDeletionInScope::DisableDeletionInScope( + MemoryTrackerImpl* owner) + : owner_(owner) { + prev_state_ = owner->MemoryDeletionEnabled(); + owner_->SetMemoryDeletionEnabled(false); +} + +MemoryTrackerImpl::DisableDeletionInScope::~DisableDeletionInScope() { + owner_->SetMemoryDeletionEnabled(prev_state_); +} + +MemoryTrackerImpl::MemoryTrackerImpl() + : thread_filter_id_(kSbThreadInvalidId) { + total_bytes_allocated_.store(0); + global_hooks_installed_ = false; + Initialize(&sb_memory_tracker_, &nb_memory_scope_reporter_); + // Push the default region so that stats can be accounted for. + PushAllocationGroup(alloc_group_map_.GetDefaultUnaccounted()); +} + +MemoryTrackerImpl::~MemoryTrackerImpl() { + // If we are currently hooked into allocation tracking... + if (global_hooks_installed_) { + RemoveGlobalTrackingHooks(); + // For performance reasons no locking is used on the tracker. + // Therefore give enough time for other threads to exit this tracker + // before fully destroying this object. + SbThreadSleep(250 * kSbTimeMillisecond); // 250 millisecond wait. + } +} + +bool MemoryTrackerImpl::AddMemoryTracking(const void* memory, size_t size) { + // Vars are stored to assist in debugging. + const bool thread_allowed_to_report = IsCurrentThreadAllowedToReport(); + const bool valid_memory_request = (memory != NULL) && (size != 0); + const bool mem_track_enabled = IsMemoryTrackingEnabled(); + + const bool tracking_enabled = + mem_track_enabled && valid_memory_request && thread_allowed_to_report; + + if (!tracking_enabled) { + return false; + } + + // End all memory tracking in subsequent data structures. + DisableMemoryTrackingInScope no_memory_tracking(this); + AllocationGroupStack* alloc_stack = + allocation_group_stack_tls_.GetOrCreate(); + AllocationGroup* group = alloc_stack->Peek(); + if (!group) { + group = alloc_group_map_.GetDefaultUnaccounted(); + } + +#ifndef NDEBUG + // This section of the code is designed to allow a developer to break + // execution whenever the debug allocation stack is in scope, so that the + // allocations can be stepped through. + // Example: + // Debug_PushAllocationGroupBreakPointByName("Javascript"); + // ...now set a break point below at "static int i = 0" + if (group && (group == alloc_stack->Peek_DebugBreak()) && + alloc_stack->Peek_DebugBreak()) { + static int i = 0; // This static is here to allow an + ++i; // easy breakpoint in the debugger + } +#endif + + AllocationRecord alloc_record(size, group); + bool added = atomic_allocation_map_.Add(memory, alloc_record); + if (added) { + AddAllocationBytes(size); + group->AddAllocation(size); + } else { + // Handles the case where the memory hasn't been properly been reported + // released. This is less serious than you would think because the memory + // allocator in the system will recycle the memory and it will come back. + AllocationRecord unexpected_alloc; + atomic_allocation_map_.Get(memory, &unexpected_alloc); + AllocationGroup* prev_group = unexpected_alloc.allocation_group; + + std::string prev_group_name; + if (prev_group) { + prev_group_name = unexpected_alloc.allocation_group->name(); + } else { + prev_group_name = "none"; + } + + if (!added) { + std::stringstream ss; + ss << "\nUnexpected condition, previous allocation was not removed:\n" + << "\tprevious alloc group: " << prev_group_name << "\n" + << "\tnew alloc group: " << group->name() << "\n" + << "\tprevious size: " << unexpected_alloc.size << "\n" + << "\tnew size: " << size << "\n"; + + SbLogRaw(ss.str().c_str()); + } + } + return added; +} + +size_t MemoryTrackerImpl::RemoveMemoryTracking(const void* memory) { + const bool do_remove = memory && MemoryDeletionEnabled(); + if (!do_remove) { + return 0; + } + + AllocationRecord alloc_record; + bool removed = false; + + // Prevent a map::erase() from causing an endless stack overflow by + // disabling memory deletion for the very limited scope. + { + // Not a correct name. TODO - change. + DisableDeletionInScope no_memory_deletion(this); + removed = atomic_allocation_map_.Remove(memory, &alloc_record); + } + + if (!removed) { + return 0; + } else { + const int64_t alloc_size = (static_cast<int64_t>(alloc_record.size)); + AllocationGroup* group = alloc_record.allocation_group; + if (group) { + group->AddAllocation(-alloc_size); + } + AddAllocationBytes(-alloc_size); + return alloc_record.size; + } +} + +bool MemoryTrackerImpl::GetMemoryTracking(const void* memory, + AllocationRecord* record) const { + const bool exists = atomic_allocation_map_.Get(memory, record); + return exists; +} + +void MemoryTrackerImpl::SetMemoryTrackingEnabled(bool on) { + memory_tracking_disabled_tls_.Set(!on); +} + +bool MemoryTrackerImpl::IsMemoryTrackingEnabled() const { + const bool enabled = !memory_tracking_disabled_tls_.Get(); + return enabled; +} + +void MemoryTrackerImpl::AddAllocationBytes(int64_t val) { + total_bytes_allocated_.fetch_add(val); +} + +bool MemoryTrackerImpl::MemoryDeletionEnabled() const { + return !memory_deletion_enabled_tls_.Get(); +} + +void MemoryTrackerImpl::SetMemoryDeletionEnabled(bool on) { + memory_deletion_enabled_tls_.Set(!on); +} + +MemoryTrackerImpl::DisableMemoryTrackingInScope::DisableMemoryTrackingInScope( + MemoryTrackerImpl* t) + : owner_(t) { + prev_value_ = owner_->IsMemoryTrackingEnabled(); + owner_->SetMemoryTrackingEnabled(false); +} + +MemoryTrackerImpl::DisableMemoryTrackingInScope:: + ~DisableMemoryTrackingInScope() { + owner_->SetMemoryTrackingEnabled(prev_value_); +} + + +MemoryTrackerPrintThread::MemoryTrackerPrintThread( + MemoryTracker* memory_tracker) + : SimpleThread("MemoryTrackerPrintThread"), + finished_(false), + memory_tracker_(memory_tracker) { + Start(); +} + +MemoryTrackerPrintThread::~MemoryTrackerPrintThread() { + Cancel(); + Join(); +} + +void MemoryTrackerPrintThread::Cancel() { + finished_.store(true); +} + +void MemoryTrackerPrintThread::Run() { + while (!finished_.load()) { + NoMemoryTracking no_mem_tracking_in_this_scope(memory_tracker_); + + // std::map<std::string, const AllocationGroup*> output; + // typedef std::map<std::string, const AllocationGroup*>::const_iterator + // MapIt; + std::vector<const AllocationGroup*> vector_output; + memory_tracker_->GetAllocationGroups(&vector_output); + + typedef std::map<std::string, const AllocationGroup*> Map; + typedef Map::const_iterator MapIt; + + Map output; + for (int i = 0; i < vector_output.size(); ++i) { + const AllocationGroup* group = vector_output[i]; + output[group->name()] = group; + } + + int32_t num_allocs = 0; + int64_t total_bytes = 0; + + struct F { + static void PrintRow(std::stringstream* ss, + const std::string& v1, + const std::string& v2, + const std::string& v3) { + ss->width(20); + *ss << std::left << v1; + ss->width(13); + *ss << std::right << v2 << " "; + ss->width(10); + *ss << std::right << v3 << "\n"; + } + }; + + if (memory_tracker_->IsMemoryTrackingEnabled()) { + // If this isn't true then it would cause an infinite loop. The + // following will likely crash. + SB_DCHECK(false) << "Unexpected, memory tracking should be disabled."; + } + + std::stringstream ss; + + F::PrintRow(&ss, "NAME", "BYTES", "NUM ALLOCS"); + ss << "---------------------------------------------\n"; + for (MapIt it = output.begin(); it != output.end(); ++it) { + const AllocationGroup* group = it->second; + if (!group) { + continue; + } + + int32_t num_group_allocs = -1; + int64_t total_group_bytes = -1; + + group->GetAggregateStats(&num_group_allocs, &total_group_bytes); + SB_DCHECK(-1 != num_group_allocs); + SB_DCHECK(-1 != total_group_bytes); + num_allocs += num_group_allocs; + total_bytes += total_group_bytes; + + F::PrintRow(&ss, + it->first, + NumberFormatWithCommas(total_group_bytes), + NumberFormatWithCommas(num_group_allocs)); + } + ss << "---------------------------------------------\n"; + + std::stringstream final_ss; + final_ss << "\n" + << "Total Bytes Allocated: " + << NumberFormatWithCommas(total_bytes) << "\n" + << "Total allocations: " + << NumberFormatWithCommas(num_allocs) << "\n\n" << ss.str(); + + SbLogRaw(final_ss.str().c_str()); + + SbThreadSleep(1000 * 1000); + } +} + +MemoryTrackerPrintCSVThread::MemoryTrackerPrintCSVThread( + MemoryTracker* memory_tracker, + int sampling_interval_ms, + int sampling_time_ms) + : SimpleThread("MemoryTrackerPrintCSVThread"), + memory_tracker_(memory_tracker), + sample_interval_ms_(sampling_interval_ms), + sampling_time_ms_(sampling_time_ms), + start_time_(SbTimeGetNow()) { + Start(); +} + + +MemoryTrackerPrintCSVThread::~MemoryTrackerPrintCSVThread() { + Cancel(); + Join(); +} + +void MemoryTrackerPrintCSVThread::Cancel() { + canceled_.store(true); +} + +std::string MemoryTrackerPrintCSVThread::ToCsvString( + const MapAllocationSamples& samples_in) { + typedef MapAllocationSamples Map; + typedef Map::const_iterator MapIt; + + const char QUOTE[] = "\""; + const char DELIM[] = ","; + const char NEW_LINE[] = "\n"; + + size_t largest_sample_size = 0; + size_t smallest_sample_size = INT_MAX; + + // Sanitize samples_in and store as samples. + MapAllocationSamples samples; + for (MapIt it = samples_in.begin(); it != samples_in.end(); ++it) { + std::string name = it->first; + const AllocationSamples& value = it->second; + + if (value.allocated_bytes_.size() != value.number_allocations_.size()) { + SB_NOTREACHED() << "Error at " << __FILE__ << ":" << __LINE__; + return "ERROR"; + } + + const size_t n = value.allocated_bytes_.size(); + if (n > largest_sample_size) { + largest_sample_size = n; + } + if (n < smallest_sample_size) { + smallest_sample_size = n; + } + + // Strip out any characters that could make parsing the csv difficult. + name = RemoveString(name, QUOTE); + name = RemoveString(name, DELIM); + name = RemoveString(name, NEW_LINE); + + const bool duplicate_found = (samples.end() != samples.find(name)); + if (duplicate_found) { + SB_NOTREACHED() << "Error, duplicate found for entry: " + << name << NEW_LINE; + } + // Store value as a sanitized sample. + samples[name] = value; + } + + SB_DCHECK(largest_sample_size == smallest_sample_size); + + std::stringstream ss; + + // Begin output to CSV. + // Sometimes we need to skip the CPU memory entry. + const MapIt total_cpu_memory_it = samples.find(UntrackedMemoryKey()); + + // Preamble + ss << NEW_LINE << "//////////////////////////////////////////////"; + ss << NEW_LINE << "// CSV of bytes / allocation" << NEW_LINE; + // HEADER. + ss << "Name" << DELIM << QUOTE << "Bytes/Alloc" << QUOTE << NEW_LINE; + // DATA. + for (MapIt it = samples.begin(); it != samples.end(); ++it) { + if (total_cpu_memory_it == it) { + continue; + } + + const AllocationSamples& samples = it->second; + if (samples.allocated_bytes_.empty() || + samples.number_allocations_.empty()) { + SB_NOTREACHED() << "Should not be here"; + return "ERROR"; + } + const int32_t n_allocs = samples.number_allocations_.back(); + const int64_t n_bytes = samples.allocated_bytes_.back(); + int bytes_per_alloc = 0; + if (n_allocs > 0) { + bytes_per_alloc = n_bytes / n_allocs; + } + const std::string& name = it->first; + ss << QUOTE << name << QUOTE << DELIM << bytes_per_alloc << NEW_LINE; + } + ss << NEW_LINE; + + // Preamble + ss << NEW_LINE << "//////////////////////////////////////////////" + << NEW_LINE << "// CSV of bytes allocated per region (MB's)." + << NEW_LINE << "// Units are in Megabytes. This is designed" + << NEW_LINE << "// to be used in a stacked graph." << NEW_LINE; + + // HEADER. + for (MapIt it = samples.begin(); it != samples.end(); ++it) { + if (total_cpu_memory_it == it) { + continue; + } + const std::string& name = it->first; + ss << QUOTE << name << QUOTE << DELIM; + } + // Save the total for last. + if (total_cpu_memory_it != samples.end()) { + const std::string& name = total_cpu_memory_it->first; + ss << QUOTE << name << QUOTE << DELIM; + } + ss << NEW_LINE; + + // Print out the values of each of the samples. + for (int i = 0; i < smallest_sample_size; ++i) { + for (MapIt it = samples.begin(); it != samples.end(); ++it) { + if (total_cpu_memory_it == it) { + continue; + } + const int64_t alloc_bytes = it->second.allocated_bytes_[i]; + // Convert to float megabytes with decimals of precision. + double n = alloc_bytes / (1000 * 10); + n = n / (100.); + ss << n << DELIM; + } + if (total_cpu_memory_it != samples.end()) { + const int64_t alloc_bytes = + total_cpu_memory_it->second.allocated_bytes_[i]; + // Convert to float megabytes with decimals of precision. + double n = alloc_bytes / (1000 * 10); + n = n / (100.); + ss << n << DELIM; + } + ss << NEW_LINE; + } + + ss << NEW_LINE; + // Preamble + ss << NEW_LINE << "//////////////////////////////////////////////"; + ss << NEW_LINE << "// CSV of number of allocations per region." << NEW_LINE; + + // HEADER + for (MapIt it = samples.begin(); it != samples.end(); ++it) { + if (total_cpu_memory_it == it) { + continue; + } + const std::string& name = it->first; + ss << QUOTE << name << QUOTE << DELIM; + } + ss << NEW_LINE; + for (int i = 0; i < smallest_sample_size; ++i) { + for (MapIt it = samples.begin(); it != samples.end(); ++it) { + if (total_cpu_memory_it == it) { + continue; + } + const int64_t n_allocs = it->second.number_allocations_[i]; + ss << n_allocs << DELIM; + } + ss << NEW_LINE; + } + std::string output = ss.str(); + return output; +} + +const char* MemoryTrackerPrintCSVThread::UntrackedMemoryKey() { + return "Untracked Memory"; +} + +void MemoryTrackerPrintCSVThread::Run() { + NoMemoryTracking no_mem_tracking_in_this_scope(memory_tracker_); + + SbLogRaw("\nMemoryTrackerPrintCSVThread is sampling...\n"); + int sample_count = 0; + MapAllocationSamples map_samples; + + while (!TimeExpiredYet() && !canceled_.load()) { + // Sample total memory used by the system. + MemoryStats mem_stats = GetProcessMemoryStats(); + int64_t untracked_used_memory = mem_stats.used_cpu_memory + + mem_stats.used_gpu_memory; + + std::vector<const AllocationGroup*> vector_output; + memory_tracker_->GetAllocationGroups(&vector_output); + + // Sample all known memory scopes. + for (int i = 0; i < vector_output.size(); ++i) { + const AllocationGroup* group = vector_output[i]; + const std::string& name = group->name(); + + const bool first_creation = map_samples.find(group->name()) == + map_samples.end(); + + AllocationSamples* new_entry = &(map_samples[name]); + + // Didn't see it before so create new entry. + if (first_creation) { + // Make up for lost samples... + new_entry->allocated_bytes_.resize(sample_count, 0); + new_entry->number_allocations_.resize(sample_count, 0); + } + + int32_t num_allocs = - 1; + int64_t allocation_bytes = -1; + group->GetAggregateStats(&num_allocs, &allocation_bytes); + + new_entry->allocated_bytes_.push_back(allocation_bytes); + new_entry->number_allocations_.push_back(num_allocs); + + untracked_used_memory -= allocation_bytes; + } + + // Now push in remaining total. + AllocationSamples* process_sample = &(map_samples[UntrackedMemoryKey()]); + if (untracked_used_memory < 0) { + // On some platforms, total GPU memory may not be correctly reported. + // However the allocations from the GPU memory may be reported. In this + // case untracked_used_memory will go negative. To protect the memory + // reporting the untracked_used_memory is set to 0 so that it doesn't + // cause an error in reporting. + untracked_used_memory = 0; + } + process_sample->allocated_bytes_.push_back(untracked_used_memory); + process_sample->number_allocations_.push_back(-1); + + ++sample_count; + SbThreadSleep(kSbTimeMillisecond * sample_interval_ms_); + } + + std::string output = ToCsvString(map_samples); + SbLogRaw(output.c_str()); + SbLogFlush(); + // Prevents the "thread exited code 0" from being interleaved into the + // output. This happens if SbLogFlush() is not implemented for the platform. + SbThreadSleep(1000 * kSbTimeMillisecond); +} + +bool MemoryTrackerPrintCSVThread::TimeExpiredYet() { + const SbTime diff_us = SbTimeGetNow() - start_time_; + const bool expired_time = diff_us > (sampling_time_ms_ * kSbTimeMillisecond); + return expired_time; +} + +} // namespace analytics +} // namespace nb
diff --git a/src/nb/analytics/memory_tracker_impl.h b/src/nb/analytics/memory_tracker_impl.h new file mode 100644 index 0000000..1af04aa --- /dev/null +++ b/src/nb/analytics/memory_tracker_impl.h
@@ -0,0 +1,233 @@ +/* + * Copyright 2016 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 NB_MEMORY_TRACKER_IMPL_H_ +#define NB_MEMORY_TRACKER_IMPL_H_ + +#include "nb/analytics/memory_tracker_helpers.h" +#include "nb/analytics/memory_tracker.h" +#include "nb/memory_scope.h" +#include "nb/scoped_ptr.h" +#include "nb/thread_local_object.h" +#include "starboard/configuration.h" +#include "starboard/memory_reporter.h" +#include "starboard/memory.h" +#include "starboard/mutex.h" +#include "starboard/time.h" + +namespace nb { +namespace analytics { + +class MemoryTrackerImpl : public MemoryTracker { + public: + typedef ConcurrentAllocationMap AllocationMapType; + + MemoryTrackerImpl(); + virtual ~MemoryTrackerImpl(); + + // MemoryTracker adapter which is compatible with the SbMemoryReporter + // interface. + SbMemoryReporter* GetMemoryReporter(); + NbMemoryScopeReporter* GetMemoryScopeReporter(); + + AllocationGroup* GetAllocationGroup(const char* name); + // Declares the start of a memory region. After this call, all + // memory regions will be tagged with this allocation group. + // Note that AllocationGroup is a tracking characteristic and + // does not imply any sort of special allocation pool. + void PushAllocationGroupByName(const char* group_name); + void PushAllocationGroup(AllocationGroup* alloc_group); + AllocationGroup* PeekAllocationGroup(); + // Ends the current memory region and the previous memory region + // is restored. + void PopAllocationGroup(); + + // CONTROL + // + // Adds tracking to the supplied memory pointer. An AllocationRecord is + // generated for the supplied allocation which can be queried immediately + // with GetMemoryTracking(...). + bool InstallGlobalTrackingHooks() SB_OVERRIDE { + if (global_hooks_installed_) + return true; + global_hooks_installed_ = true; + bool ok = SbMemorySetReporter(GetMemoryReporter()); + ok |= NbSetMemoryScopeReporter(GetMemoryScopeReporter()); + return ok; + } + void RemoveGlobalTrackingHooks() SB_OVERRIDE { + SbMemorySetReporter(NULL); + NbSetMemoryScopeReporter(NULL); + } + + bool AddMemoryTracking(const void* memory, size_t size) SB_OVERRIDE; + size_t RemoveMemoryTracking(const void* memory) SB_OVERRIDE; + // Returns true if the allocation record was successfully found. + // If true then the output will be written to with the values. + // Otherwise the output is reset to the empty AllocationRecord. + bool GetMemoryTracking(const void* memory, + AllocationRecord* record) const SB_OVERRIDE; + // Thread local function to get and set the memory tracking state. When set + // to disabled then memory allocations are not recorded. However memory + // deletions are still recorded. + void SetMemoryTrackingEnabled(bool on) SB_OVERRIDE; + bool IsMemoryTrackingEnabled() const SB_OVERRIDE; + + // REPORTING + // + // Total allocation bytes that have been allocated by this + // MemoryTrackerImpl. + int64_t GetTotalAllocationBytes() SB_OVERRIDE; + // Retrieves a collection of all known allocation groups. Locking is done + // internally. + void GetAllocationGroups(std::vector<const AllocationGroup*>* output) + SB_OVERRIDE; + // Retrieves a collection of all known allocation groups. Locking is done + // internally. The output is a map of names to AllocationGroups. + void GetAllocationGroups( + std::map<std::string, const AllocationGroup*>* output); + + // Provides access to the internal allocations in a thread safe way. + // Allocation tracking is disabled in the current thread for the duration + // of the visitation. + void Accept(AllocationVisitor* visitor) SB_OVERRIDE; + + int64_t GetTotalNumberOfAllocations() SB_OVERRIDE { + return pointer_map()->Size(); + } + + // TESTING. + AllocationMapType* pointer_map() { return &atomic_allocation_map_; } + void Clear(); + + // This is useful for debugging. Allows the developer to set a breakpoint + // and see only allocations that are in the defined allocation group. This + // is only active in the current thread. + void Debug_PushAllocationGroupBreakPointByName(const char* group_name); + void Debug_PushAllocationGroupBreakPoint(AllocationGroup* alloc_group); + void Debug_PopAllocationGroupBreakPoint(); + + // This is useful for testing, setting this to a thread will allow ONLY + // those allocations from the set thread. + // Setting this to kSbThreadInvalidId (default) allows all threads to report + // allocations. + void SetThreadFilter(SbThreadId tid); + bool IsCurrentThreadAllowedToReport() const; + + private: + struct DisableMemoryTrackingInScope { + DisableMemoryTrackingInScope(MemoryTrackerImpl* t); + ~DisableMemoryTrackingInScope(); + MemoryTrackerImpl* owner_; + bool prev_value_; + }; + + // Disables all memory deletion in the current scope. This is used in one + // location. + struct DisableDeletionInScope { + DisableDeletionInScope(MemoryTrackerImpl* owner); + ~DisableDeletionInScope(); + MemoryTrackerImpl* owner_; + bool prev_state_; + }; + + // These are functions that are used specifically SbMemoryReporter. + static void OnMalloc(void* context, const void* memory, size_t size); + static void OnDealloc(void* context, const void* memory); + static void OnMapMem(void* context, const void* memory, size_t size); + static void OnUnMapMem(void* context, const void* memory, size_t size); + static void OnPushAllocationGroup(void* context, + NbMemoryScopeInfo* memory_scope_info); + static void OnPopAllocationGroup(void* context); + + void Initialize(SbMemoryReporter* memory_reporter, + NbMemoryScopeReporter* nb_memory_scope_reporter); + void AddAllocationBytes(int64_t val); + bool MemoryDeletionEnabled() const; + + void SetMemoryDeletionEnabled(bool on); + + SbMemoryReporter sb_memory_tracker_; + NbMemoryScopeReporter nb_memory_scope_reporter_; + SbThreadId thread_filter_id_; + + AllocationMapType atomic_allocation_map_; + AtomicStringAllocationGroupMap alloc_group_map_; + + atomic_int64_t total_bytes_allocated_; + + // THREAD LOCAL SECTION. + ThreadLocalBoolean memory_deletion_enabled_tls_; + ThreadLocalBoolean memory_tracking_disabled_tls_; + ThreadLocalObject<AllocationGroupStack> allocation_group_stack_tls_; + bool global_hooks_installed_; +}; + +// Start() is called when this object is created, and Cancel() & Join() are +// called during destruction. +class MemoryTrackerPrintThread : public SimpleThread { + public: + MemoryTrackerPrintThread(MemoryTracker* memory_tracker); + virtual ~MemoryTrackerPrintThread() SB_OVERRIDE; + + // Overridden so that the thread can exit gracefully. + virtual void Cancel() SB_OVERRIDE; + virtual void Run() SB_OVERRIDE; + + private: + atomic_bool finished_; + MemoryTracker* memory_tracker_; +}; + +// Generates CSV values of the engine. +// There are three sections of data including: +// 1. average bytes / alloc +// 2. # Bytes allocated per memory scope. +// 3. # Allocations per memory scope. +// This data can be pasted directly into a Google spreadsheet and visualized. +// Note that this thread will implicitly call Start() is called during +// construction and Cancel() & Join() during destruction. +class MemoryTrackerPrintCSVThread : public SimpleThread { + public: + MemoryTrackerPrintCSVThread(MemoryTracker* memory_tracker, + int sampling_interval_ms, + int sampling_time_ms); + virtual ~MemoryTrackerPrintCSVThread() SB_OVERRIDE; + + // Overridden so that the thread can exit gracefully. + virtual void Cancel() SB_OVERRIDE; + virtual void Run() SB_OVERRIDE; + private: + struct AllocationSamples { + std::vector<int32_t> number_allocations_; + std::vector<int64_t> allocated_bytes_; + }; + typedef std::map<std::string, AllocationSamples> MapAllocationSamples; + static std::string ToCsvString(const MapAllocationSamples& samples); + static const char* UntrackedMemoryKey(); + bool TimeExpiredYet(); + + MemoryTracker* memory_tracker_; + const int sample_interval_ms_; + const int sampling_time_ms_; + SbTime start_time_; + atomic_bool canceled_; +}; + +} // namespace analytics +} // namespace nb + +#endif // NB_MEMORY_TRACKER_IMPL_H_
diff --git a/src/nb/analytics/memory_tracker_impl_test.cc b/src/nb/analytics/memory_tracker_impl_test.cc new file mode 100644 index 0000000..309d771 --- /dev/null +++ b/src/nb/analytics/memory_tracker_impl_test.cc
@@ -0,0 +1,802 @@ +/* + * Copyright 2016 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 "nb/analytics/memory_tracker_impl.h" +#include "nb/memory_scope.h" +#include "nb/scoped_ptr.h" +#include "nb/test_thread.h" +#include "starboard/system.h" +#include "testing/gtest/include/gtest/gtest.h" + +#define STRESS_TEST_DURATION_SECONDS 1 +#define NUM_STRESS_TEST_THREADS 3 + +namespace nb { +namespace analytics { +namespace { + +MemoryTrackerImpl* s_memory_tracker_ = NULL; + +struct NoMemTracking { + bool prev_val; + NoMemTracking() : prev_val(false) { + if (s_memory_tracker_) { + prev_val = s_memory_tracker_->IsMemoryTrackingEnabled(); + s_memory_tracker_->SetMemoryTrackingEnabled(false); + } + } + ~NoMemTracking() { + if (s_memory_tracker_) { + s_memory_tracker_->SetMemoryTrackingEnabled(prev_val); + } + } +}; + +// EXPECT_XXX and ASSERT_XXX allocate memory, a big no-no when +// for unit testing allocations. These overrides disable memory +// tracking for the duration of the EXPECT and ASSERT operations. +#define EXPECT_EQ_NO_TRACKING(A, B) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_EQ(A, B); \ + } + +#define EXPECT_TRUE_NO_TRACKING(A) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_TRUE(A); \ + } + +#define EXPECT_FALSE_NO_TRACKING(A) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_FALSE(A); \ + } + +#define ASSERT_EQ_NO_TRACKING(A, B) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + ASSERT_EQ(A, B); \ + } + +#define ASSERT_TRUE_NO_TRACKING(A) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + ASSERT_TRUE(A); \ + } + +// !! converts int -> bool. +bool FlipCoin() { + return !!(SbSystemGetRandomUInt64() & 0x1); +} + +/////////////////////////////////////////////////////////////////////////////// +// Stress testing the Allocation Tracker. +class MemoryScopeThread : public nb::TestThread { + public: + typedef nb::TestThread Super; + + explicit MemoryScopeThread(MemoryTrackerImpl* memory_tracker) + : memory_tracker_(memory_tracker) { + static int s_counter = 0; + + std::stringstream ss; + ss << "MemoryScopeThread_" << s_counter++; + unique_name_ = ss.str(); + } + virtual ~MemoryScopeThread() {} + + // Overridden so that the thread can exit gracefully. + virtual void Join() { + finished_ = true; + Super::Join(); + } + virtual void Run() { + while (!finished_) { + TRACK_MEMORY_SCOPE_DYNAMIC(unique_name_.c_str()); + AllocationGroup* group = memory_tracker_->PeekAllocationGroup(); + + const int cmp_result = group->name().compare(unique_name_); + if (cmp_result != 0) { + GTEST_FAIL() << "unique name mismatch"; + return; + } + } + } + + private: + MemoryTrackerImpl* memory_tracker_; + bool finished_; + std::string unique_name_; + int do_delete_counter_; + int do_malloc_counter_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Stress testing the Allocation Tracker. +class AllocationStressThread : public nb::TestThread { + public: + explicit AllocationStressThread(MemoryTrackerImpl* memory_tracker); + virtual ~AllocationStressThread(); + + // Overridden so that the thread can exit gracefully. + virtual void Join(); + virtual void Run(); + + private: + typedef std::map<const void*, AllocationRecord> AllocMap; + + void CheckPointers(); + bool RemoveRandomAllocation(std::pair<const void*, AllocationRecord>* output); + bool DoDelete(); + void DoMalloc(); + + MemoryTrackerImpl* memory_tracker_; + bool finished_; + std::map<const void*, AllocationRecord> allocated_pts_; + std::string unique_name_; + int do_delete_counter_; + int do_malloc_counter_; +}; + +class AddAllocationStressThread : public nb::TestThread { + public: + typedef std::map<const void*, AllocationRecord> AllocMap; + + AddAllocationStressThread(MemoryTracker* memory_tracker, + int num_elements_add, + AllocMap* destination_map, + starboard::Mutex* destination_map_mutex) + : memory_tracker_(memory_tracker), + num_elements_to_add_(num_elements_add), + destination_map_(destination_map), + destination_map_mutex_(destination_map_mutex) {} + + virtual void Run() { + for (int i = 0; i < num_elements_to_add_; ++i) { + const int alloc_size = std::rand() % 100 + 8; + void* ptr = SbMemoryAllocate(alloc_size); + + AllocationRecord record; + if (memory_tracker_->GetMemoryTracking(ptr, &record)) { + NoMemTracking no_mem_tracking; // simplifies test. + + starboard::ScopedLock lock(*destination_map_mutex_); + destination_map_->insert(std::make_pair(ptr, record)); + } else { + ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not add pointer."; + } + if (FlipCoin()) { + SbThreadYield(); // Give other threads a chance to run. + } + } + } + + private: + MemoryTracker* memory_tracker_; + AllocMap* destination_map_; + starboard::Mutex* destination_map_mutex_; + int num_elements_to_add_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Framework which initializes the MemoryTracker once and installs it +// for the first test and the removes the MemoryTracker after the +// the last test finishes. +class MemoryTrackerImplTest : public ::testing::Test { + public: + typedef MemoryTrackerImpl::AllocationMapType AllocationMapType; + MemoryTrackerImplTest() {} + + MemoryTrackerImpl* memory_tracker() { return s_memory_tracker_; } + + bool GetAllocRecord(void* alloc_memory, AllocationRecord* output) { + return memory_tracker()->GetMemoryTracking(alloc_memory, output); + } + + AllocationMapType* pointer_map() { return memory_tracker()->pointer_map(); } + + size_t NumberOfAllocations() { + AllocationMapType* map = pointer_map(); + return map->Size(); + } + + int64_t TotalAllocationBytes() { + return memory_tracker()->GetTotalAllocationBytes(); + } + + bool MemoryTrackerEnabled() const { return s_memory_tracker_enabled_; } + + protected: + static void SetUpTestCase() { + if (!s_memory_tracker_) { + s_memory_tracker_ = new MemoryTrackerImpl; + s_memory_tracker_enabled_ = + s_memory_tracker_->InstallGlobalTrackingHooks(); + } + } + static void TearDownTestCase() { SbMemorySetReporter(NULL); } + + virtual void SetUp() { + memory_tracker()->Clear(); + memory_tracker()->SetThreadFilter(SbThreadGetId()); + } + + virtual void TearDown() { memory_tracker()->Clear(); } + static bool s_memory_tracker_enabled_; +}; +bool MemoryTrackerImplTest::s_memory_tracker_enabled_ = false; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryTrackerImplTest +TEST_F(MemoryTrackerImplTest, NoMemTracking) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + ASSERT_EQ_NO_TRACKING(0, NumberOfAllocations()); + scoped_ptr<int> dummy(new int()); + EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations()); + { + // Now that memory allocation is disabled, no more allocations should + // be recorded. + NoMemTracking no_memory_tracking_in_this_scope; + int* dummy2 = new int(); + EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations()); + delete dummy2; + EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations()); + } + scoped_ptr<int> dummy2(new int()); + EXPECT_EQ_NO_TRACKING(2, NumberOfAllocations()); + dummy.reset(NULL); + EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations()); + dummy2.reset(NULL); + EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations()); +} + +TEST_F(MemoryTrackerImplTest, RemovePointerOnNoMemoryTracking) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + + int* int_ptr = new int(); + { + NoMemTracking no_memory_tracking_in_this_scope; + delete int_ptr; + } + EXPECT_FALSE_NO_TRACKING(pointer_map()->Get(int_ptr, NULL)); +} + +TEST_F(MemoryTrackerImplTest, NewDeleteOverridenTest) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations()); + int* int_a = new int(0); + EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations()); + delete int_a; + EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations()); +} + +TEST_F(MemoryTrackerImplTest, TotalAllocationBytes) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + int32_t* int_a = new int32_t(0); + EXPECT_EQ_NO_TRACKING(1, NumberOfAllocations()); + EXPECT_EQ_NO_TRACKING(4, TotalAllocationBytes()); + delete int_a; + EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations()); +} + +// Tests the expectation that a lot of allocations can be executed and that +// internal data structures won't overflow. +TEST_F(MemoryTrackerImplTest, NoStackOverflow) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + static const int kNumAllocations = 1000; + std::vector<int*> allocations; + + // Also it turns out that this test is great for catching + // background threads pushing allocations through the allocator. + // This is supposed to be filtered, but if it's not then this test will + // fail. + // SbThreadYield() is used to give other threads a chance to enter into our + // allocator and catch a test failure. + SbThreadSleep(1); + ASSERT_EQ_NO_TRACKING(0, NumberOfAllocations()); + + for (int i = 0; i < kNumAllocations; ++i) { + SbThreadYield(); + EXPECT_EQ_NO_TRACKING(i, NumberOfAllocations()); + int* val = new int(0); + NoMemTracking no_tracking_in_scope; + allocations.push_back(val); + } + + EXPECT_EQ_NO_TRACKING(kNumAllocations, NumberOfAllocations()); + + for (int i = 0; i < kNumAllocations; ++i) { + SbThreadYield(); + EXPECT_EQ_NO_TRACKING(kNumAllocations - i, NumberOfAllocations()); + delete allocations[i]; + } + + EXPECT_EQ_NO_TRACKING(0, NumberOfAllocations()); +} + +// Tests the expectation that the macros will push/pop the memory scope. +TEST_F(MemoryTrackerImplTest, MacrosPushPop) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + scoped_ptr<int> dummy; + { + TRACK_MEMORY_SCOPE("TestAllocations"); + dummy.reset(new int()); + } + + scoped_ptr<int> dummy2(new int()); + + AllocationRecord alloc_rec; + pointer_map()->Get(dummy.get(), &alloc_rec); + + ASSERT_TRUE_NO_TRACKING(alloc_rec.allocation_group); + EXPECT_EQ_NO_TRACKING(std::string("TestAllocations"), + alloc_rec.allocation_group->name()); + + pointer_map()->Get(dummy2.get(), &alloc_rec); + + ASSERT_TRUE_NO_TRACKING(alloc_rec.allocation_group); + EXPECT_EQ_NO_TRACKING(std::string("Unaccounted"), + alloc_rec.allocation_group->name()); +} + +// Tests the expectation that if the cached flag on the NbMemoryScopeInfo is +// set to false that the caching of the handle is not performed. +TEST_F(MemoryTrackerImplTest, RespectsNonCachedHandle) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + const bool kCaching = false; + NbMemoryScopeInfo memory_scope = { + NULL, "MyName", __FILE__, + __LINE__, __FUNCTION__, false}; // false to disallow caching. + + // Pushing the memory scope should trigger the caching operation to be + // attempted. However, because caching was explicitly disabled this handle + // should retain the value of 0. + NbPushMemoryScope(&memory_scope); + EXPECT_TRUE_NO_TRACKING(memory_scope.cached_handle_ == NULL); + + // ... and still assert that the group was created with the expected name. + AllocationGroup* group = memory_tracker()->GetAllocationGroup("MyName"); + // Equality check. + EXPECT_EQ_NO_TRACKING(0, group->name().compare("MyName")); + NbPopMemoryScope(); +} + +// Tests the expectation that if the cached flag on the NbMemoryScopeInfo is +// set to true that the caching will be applied for the cached_handle of the +// memory scope. +TEST_F(MemoryTrackerImplTest, PushAllocGroupCachedHandle) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + NbMemoryScopeInfo memory_scope = { + NULL, // Cached handle. + "MyName", // Memory scope name. + __FILE__, __LINE__, __FUNCTION__, + true // Allows caching. + }; + + NbPushMemoryScope(&memory_scope); + EXPECT_TRUE_NO_TRACKING(memory_scope.cached_handle_ != NULL); + AllocationGroup* group = memory_tracker()->GetAllocationGroup("MyName"); + + EXPECT_EQ_NO_TRACKING(memory_scope.cached_handle_, + static_cast<void*>(group)); +} + +// Tests the expectation that the macro TRACK_MEMORY_SCOPE will capture the +// allocation in the MemoryTrackerImpl. +TEST_F(MemoryTrackerImplTest, MacrosGroupAccounting) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + MemoryTrackerImpl* track_alloc = memory_tracker(); // Debugging. + track_alloc->Clear(); + + memory_tracker()->Clear(); + const AllocationGroup* group_a = + memory_tracker()->GetAllocationGroup("MemoryTrackerTest-ScopeA"); + + const AllocationGroup* group_b = + memory_tracker()->GetAllocationGroup("MemoryTrackerTest-ScopeB"); + + ASSERT_TRUE_NO_TRACKING(group_a); + ASSERT_TRUE_NO_TRACKING(group_b); + + int32_t num_allocations = -1; + int64_t allocation_bytes = -1; + + // Expect that both groups have no allocations in them. + group_a->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(0, num_allocations); + EXPECT_EQ_NO_TRACKING(0, allocation_bytes); + + group_b->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(0, num_allocations); + EXPECT_EQ_NO_TRACKING(0, allocation_bytes); + + scoped_ptr<int> alloc_a, alloc_b, alloc_b2; + { + TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeA"); + alloc_a.reset(new int()); + { + TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeB"); + alloc_b.reset(new int()); + alloc_b2.reset(new int()); + + group_a->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(1, num_allocations); + EXPECT_EQ_NO_TRACKING(4, allocation_bytes); + alloc_a.reset(NULL); + group_a->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(0, num_allocations); + EXPECT_EQ_NO_TRACKING(0, allocation_bytes); + + num_allocations = allocation_bytes = -1; + group_b->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(2, num_allocations); + EXPECT_EQ_NO_TRACKING(8, allocation_bytes); + + alloc_b2.reset(NULL); + group_b->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(1, num_allocations); + EXPECT_EQ_NO_TRACKING(4, allocation_bytes); + + alloc_b.reset(NULL); + group_b->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(0, num_allocations); + EXPECT_EQ_NO_TRACKING(0, allocation_bytes); + } + } +} + +// Tests the expectation that the visitor can access the allocations. +TEST_F(MemoryTrackerImplTest, VisitorAccess) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + class SimpleVisitor : public AllocationVisitor { + public: + SimpleVisitor() : num_memory_allocs_(0) {} + virtual bool Visit(const void* memory, + const AllocationRecord& alloc_record) { + num_memory_allocs_++; + return true; // Keep traversing. + } + + size_t num_memory_allocs_; + }; + + SimpleVisitor visitor; + scoped_ptr<int> int_ptr(new int); + + // Should see the int_ptr allocation. + memory_tracker()->Accept(&visitor); + EXPECT_EQ_NO_TRACKING(1, visitor.num_memory_allocs_); + visitor.num_memory_allocs_ = 0; + + int_ptr.reset(NULL); + // Now no allocations should be available. + memory_tracker()->Accept(&visitor); + EXPECT_EQ_NO_TRACKING(0, visitor.num_memory_allocs_); +} + +// A stress test that rapidly adds allocations, but saves all deletions +// for the main thread. This test will catch concurrency errors related +// to reporting new allocations. +TEST_F(MemoryTrackerImplTest, MultiThreadedStressAddTest) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + // Disable allocation filtering. + memory_tracker()->SetThreadFilter(kSbThreadInvalidId); + + std::vector<nb::TestThread*> threads; + + const int kNumObjectsToAdd = 10000 / NUM_STRESS_TEST_THREADS; + AddAllocationStressThread::AllocMap map; + starboard::Mutex map_mutex; + + for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) { + nb::TestThread* thread = new AddAllocationStressThread( + memory_tracker(), kNumObjectsToAdd, &map, &map_mutex); + + threads.push_back(thread); + } + + for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) { + threads[i]->Start(); + } + + for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) { + threads[i]->Join(); + } + for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) { + delete threads[i]; + } + + while (!map.empty()) { + const void* ptr = map.begin()->first; + map.erase(map.begin()); + + if (!memory_tracker()->GetMemoryTracking(ptr, NULL)) { + ADD_FAILURE_AT(__FILE__, __LINE__) << "No tracking?!"; + } + + SbMemoryDeallocate(const_cast<void*>(ptr)); + if (memory_tracker()->GetMemoryTracking(ptr, NULL)) { + ADD_FAILURE_AT(__FILE__, __LINE__) << "Tracking?!"; + } + } +} + +// Tests the expectation that memory scopes are multi-threaded safe. +TEST_F(MemoryTrackerImplTest, MultiThreadedMemoryScope) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + memory_tracker()->SetThreadFilter(kSbThreadInvalidId); + TRACK_MEMORY_SCOPE("MultiThreadedStressUseTest"); + + std::vector<MemoryScopeThread*> threads; + + for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) { + threads.push_back(new MemoryScopeThread(memory_tracker())); + } + + for (int i = 0; i < threads.size(); ++i) { + threads[i]->Start(); + } + + SbThreadSleep(STRESS_TEST_DURATION_SECONDS * 1000 * 1000); + + for (int i = 0; i < threads.size(); ++i) { + threads[i]->Join(); + } + + for (int i = 0; i < threads.size(); ++i) { + delete threads[i]; + } + + threads.clear(); +} + +// Tests the expectation that new/delete can be done by different threads. +TEST_F(MemoryTrackerImplTest, MultiThreadedStressUseTest) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + // Disable allocation filtering. + memory_tracker()->SetThreadFilter(kSbThreadInvalidId); + TRACK_MEMORY_SCOPE("MultiThreadedStressUseTest"); + + std::vector<AllocationStressThread*> threads; + + for (int i = 0; i < NUM_STRESS_TEST_THREADS; ++i) { + threads.push_back(new AllocationStressThread(memory_tracker())); + } + + for (int i = 0; i < threads.size(); ++i) { + threads[i]->Start(); + } + + SbThreadSleep(STRESS_TEST_DURATION_SECONDS * 1000 * 1000); + + for (int i = 0; i < threads.size(); ++i) { + threads[i]->Join(); + } + + for (int i = 0; i < threads.size(); ++i) { + delete threads[i]; + } + + threads.clear(); +} + +//////////////////////////// Implementation /////////////////////////////////// +/// Impl of AllocationStressThread +AllocationStressThread::AllocationStressThread(MemoryTrackerImpl* tracker) + : memory_tracker_(tracker), finished_(false) { + static int counter = 0; + std::stringstream ss; + ss << "AllocStressThread-" << counter++; + unique_name_ = ss.str(); +} + +AllocationStressThread::~AllocationStressThread() { + if (!allocated_pts_.empty()) { + ADD_FAILURE_AT(__FILE__, __LINE__) << "allocated pointers still exist"; + } +} + +void AllocationStressThread::Join() { + finished_ = true; + nb::TestThread::Join(); +} + +void AllocationStressThread::CheckPointers() { + typedef AllocMap::iterator Iter; + + for (Iter it = allocated_pts_.begin(); it != allocated_pts_.end(); ++it) { + const void* ptr = it->first; + const bool found = memory_tracker_->GetMemoryTracking(ptr, NULL); + if (!found) { + NoMemTracking no_tracking_in_scope; + ADD_FAILURE_AT(__FILE__, __LINE__) << "Not found"; + } + } +} + +void AllocationStressThread::Run() { + while (!finished_) { + const bool do_delete = FlipCoin(); + if (FlipCoin()) { + DoDelete(); + } else { + DoMalloc(); + } + CheckPointers(); + + // Randomly give other threads the opportunity run. + if (FlipCoin()) { + SbThreadYield(); + } + } + + // Clear out all memory. + while (DoDelete()) { + ; + } +} + +bool AllocationStressThread::RemoveRandomAllocation( + std::pair<const void*, AllocationRecord>* output) { + if (allocated_pts_.empty()) { + return false; + } + + // Select a random pointer to delete. + int idx = std::rand() % allocated_pts_.size(); + AllocMap::iterator iter = allocated_pts_.begin(); + while (idx > 0) { + idx--; + iter++; + } + output->first = iter->first; + output->second = iter->second; + allocated_pts_.erase(iter); + return true; +} + +bool AllocationStressThread::DoDelete() { + NoMemTracking no_memory_tracking_in_this_scope; + ++do_delete_counter_; + + std::pair<const void*, AllocationRecord> alloc; + if (!RemoveRandomAllocation(&alloc)) { + return false; + } + + const void* ptr = alloc.first; + const AllocationRecord expected_alloc_record = alloc.second; + + TRACK_MEMORY_SCOPE_DYNAMIC(unique_name_.c_str()); + AllocationGroup* current_group = memory_tracker_->PeekAllocationGroup(); + + // Expect that the name of the current allocation group name is the same as + // what we expect. + if (current_group->name() != unique_name_) { + NoMemTracking no_memory_tracking_in_this_scope; + ADD_FAILURE_AT(__FILE__, __LINE__) << " " << current_group->name() + << " != " << unique_name_; + } + + MemoryTrackerImpl::AllocationMapType* internal_alloc_map = + memory_tracker_->pointer_map(); + + AllocationRecord existing_alloc_record; + + const bool found_existing_record = + memory_tracker_->GetMemoryTracking(ptr, &existing_alloc_record); + + if (!found_existing_record) { + ADD_FAILURE_AT(__FILE__, __LINE__) + << "expected to find existing record, but did not"; + } else if (current_group != existing_alloc_record.allocation_group) { + ADD_FAILURE_AT(__FILE__, __LINE__) + << "group allocation mismatch: " << current_group->name() + << " != " << existing_alloc_record.allocation_group->name() << "\n"; + } + SbMemoryDeallocate(const_cast<void*>(ptr)); + return true; +} + +void AllocationStressThread::DoMalloc() { + ++do_malloc_counter_; + if (allocated_pts_.size() > 10000) { + return; + } + + TRACK_MEMORY_SCOPE_DYNAMIC(unique_name_.c_str()); + AllocationGroup* current_group = memory_tracker_->PeekAllocationGroup(); + + // Sanity check, make sure that the current_group name is the same as + // our unique name. + if (current_group->name() != unique_name_) { + NoMemTracking no_tracking_in_scope; + ADD_FAILURE_AT(__FILE__, __LINE__) << " " << current_group->name() + << " != " << unique_name_; + } + + if (!memory_tracker_->IsMemoryTrackingEnabled()) { + NoMemTracking no_tracking_in_scope; + ADD_FAILURE_AT(__FILE__, __LINE__) + << " memory tracking state was disabled."; + } + + const int alloc_size = std::rand() % 100 + 8; + + void* memory = SbMemoryAllocate(alloc_size); + + AllocationRecord record; + bool found = memory_tracker_->GetMemoryTracking(memory, &record); + if (!found) { + NoMemTracking no_tracking_in_scope; + ADD_FAILURE_AT(__FILE__, __LINE__) + << "Violated expectation, malloc counter: " << do_malloc_counter_; + } + AllocMap::iterator found_it = allocated_pts_.find(memory); + + if (found_it != allocated_pts_.end()) { + NoMemTracking no_tracking_in_scope; + ADD_FAILURE_AT(__FILE__, __LINE__) + << "This pointer should not be in the map."; + } + + NoMemTracking no_tracking_in_scope; + allocated_pts_[memory] = AllocationRecord(alloc_size, current_group); +} + +} // namespace +} // namespace analytics +} // namespace nb
diff --git a/src/nb/analytics/memory_tracker_test.cc b/src/nb/analytics/memory_tracker_test.cc new file mode 100644 index 0000000..92cb8b3 --- /dev/null +++ b/src/nb/analytics/memory_tracker_test.cc
@@ -0,0 +1,222 @@ +/* + * Copyright 2016 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 "nb/analytics/memory_tracker.h" +#include "nb/analytics/memory_tracker_helpers.h" +#include "nb/memory_scope.h" +#include "nb/scoped_ptr.h" +#include "starboard/memory.h" +#include "starboard/memory_reporter.h" +#include "starboard/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace nb { +namespace analytics { +namespace { + +MemoryTracker* s_memory_tracker_ = NULL; + +struct NoMemTracking { + bool prev_val; + NoMemTracking() : prev_val(false) { + if (s_memory_tracker_) { + prev_val = s_memory_tracker_->IsMemoryTrackingEnabled(); + s_memory_tracker_->SetMemoryTrackingEnabled(false); + } + } + ~NoMemTracking() { + if (s_memory_tracker_) { + s_memory_tracker_->SetMemoryTrackingEnabled(prev_val); + } + } +}; + +// EXPECT_XXX and ASSERT_XXX allocate memory, a big no-no when +// for unit testing allocations. These overrides disable memory +// tracking for the duration of the EXPECT and ASSERT operations. +#define EXPECT_EQ_NO_TRACKING(A, B) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_EQ(A, B); \ + } + +#define EXPECT_TRUE_NO_TRACKING(A) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_TRUE(A); \ + } + +#define EXPECT_FALSE_NO_TRACKING(A) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_FALSE(A); \ + } + +#define ASSERT_TRUE_NO_TRACKING(A) \ + { \ + NoMemTracking no_memory_tracking_in_this_scope; \ + ASSERT_TRUE(A); \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Framework which initializes the MemoryTracker once and installs it +// for the first test and the removes the MemoryTracker after the +// the last test finishes. +class MemoryTrackerTest : public ::testing::Test { + public: + MemoryTrackerTest() {} + + MemoryTracker* memory_tracker() { return s_memory_tracker_; } + + bool GetAllocRecord(void* alloc_memory, AllocationRecord* output) { + return memory_tracker()->GetMemoryTracking(alloc_memory, output); + } + + int64_t TotalNumberOfAllocations() { + return memory_tracker()->GetTotalNumberOfAllocations(); + } + + int64_t TotalAllocationBytes() { + return memory_tracker()->GetTotalAllocationBytes(); + } + + bool MemoryTrackerEnabled() const { return s_memory_tracker_enabled_; } + + protected: + static void SetUpTestCase() { + s_memory_tracker_ = MemoryTracker::Get(); + s_memory_tracker_enabled_ = s_memory_tracker_->InstallGlobalTrackingHooks(); + } + static void TearDownTestCase() { + s_memory_tracker_->RemoveGlobalTrackingHooks(); + } + static bool s_memory_tracker_enabled_; +}; +bool MemoryTrackerTest::s_memory_tracker_enabled_ = false; + +/////////////////////////////////////////////////////////////////////////////// +class FindAllocationVisitor : public AllocationVisitor { + public: + FindAllocationVisitor() : found_(false), memory_to_find_(NULL) {} + + bool found() const { return found_; } + void set_found(bool val) { found_ = val; } + void set_memory_to_find(const void* memory) { + memory_to_find_ = memory; + found_ = false; + } + + virtual bool Visit(const void* memory, const AllocationRecord& alloc_record) { + if (memory_to_find_ == memory) { + found_ = true; + return false; + } + return true; + } + + private: + bool found_; + const void* memory_to_find_; +}; + +/////////////////////////////////////////////////////////////////////////////// +TEST_F(MemoryTrackerTest, MacrosScopedObject) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + + scoped_ptr<int> alloc_a, alloc_b; + { + TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeA"); + alloc_a.reset(new int()); + { + TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeB"); + alloc_b.reset(new int()); + } + } + + // Now test that the allocations now exist in the memory tracker. + AllocationRecord alloc_record_a, alloc_record_b; + // Expect that the allocations exist and that the AllocRecords are written + // with the allocation information. + EXPECT_TRUE_NO_TRACKING( + memory_tracker()->GetMemoryTracking(alloc_a.get(), &alloc_record_a)); + EXPECT_TRUE_NO_TRACKING( + memory_tracker()->GetMemoryTracking(alloc_b.get(), &alloc_record_b)); + + // Sanity test that the allocations are non-null. + + const AllocationGroup* group_a = alloc_record_a.allocation_group; + const AllocationGroup* group_b = alloc_record_b.allocation_group; + ASSERT_TRUE_NO_TRACKING(group_a); + ASSERT_TRUE_NO_TRACKING(group_b); + + EXPECT_EQ_NO_TRACKING(group_a->name(), + std::string("MemoryTrackerTest-ScopeA")); + EXPECT_EQ_NO_TRACKING(group_b->name(), + std::string("MemoryTrackerTest-ScopeB")); + + // When the allocation is returned to the free store then it's expected that + // the memory tracker will indicate that the allocation no longer exists. + alloc_a.reset(); + alloc_b.reset(); + + EXPECT_FALSE_NO_TRACKING( + memory_tracker()->GetMemoryTracking(alloc_a.get(), &alloc_record_a)); + EXPECT_FALSE_NO_TRACKING( + memory_tracker()->GetMemoryTracking(alloc_b.get(), &alloc_record_b)); + + int32_t num_allocations = -1; + int64_t allocation_bytes = -1; + + group_a->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(0, num_allocations); + EXPECT_EQ_NO_TRACKING(0, allocation_bytes); + + group_b->GetAggregateStats(&num_allocations, &allocation_bytes); + EXPECT_EQ_NO_TRACKING(0, num_allocations); + EXPECT_EQ_NO_TRACKING(0, allocation_bytes); +} + +/////////////////////////////////////////////////////////////////////////////// +TEST_F(MemoryTrackerTest, Visitor) { + // Memory tracker is not enabled for this build. + if (!MemoryTrackerEnabled()) { + return; + } + + FindAllocationVisitor visitor; + + scoped_ptr<int> alloc_a; + { + TRACK_MEMORY_SCOPE("MemoryTrackerTest-ScopeA"); + + alloc_a.reset(new int()); + visitor.set_memory_to_find(alloc_a.get()); + memory_tracker()->Accept(&visitor); + EXPECT_TRUE_NO_TRACKING(visitor.found()); + + alloc_a.reset(NULL); + visitor.set_found(false); + memory_tracker()->Accept(&visitor); + EXPECT_FALSE_NO_TRACKING(visitor.found()); + } +} + +} // namespace +} // namespace analytics +} // namespace nb
diff --git a/src/nb/atomic.h b/src/nb/atomic.h new file mode 100644 index 0000000..9bd5c61 --- /dev/null +++ b/src/nb/atomic.h
@@ -0,0 +1,281 @@ +/* + * Copyright 2016 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 NB_ATOMIC_H_ +#define NB_ATOMIC_H_ + +#include "starboard/mutex.h" +#include "starboard/types.h" + +namespace nb { + +// Provides atomic types like integer and float. Some types like atomic_int32_t +// are likely to be hardware accelerated for your platform. +// +// Never use the parent types like atomic<T>, atomic_number<T> or +// atomic_integral<T> and instead use the fully qualified classes like +// atomic_int32_t, atomic_pointer<T*>, etc. +// +// Note on template instantiation, avoid using the parent type and instead +// use the fully qualified type. +// BAD: +// template<typename T> +// void Foo(const atomic<T>& value); +// GOOD: +// template<typename atomic_t> +// void Foo(const atomic_t& vlaue); + +// Atomic Pointer class. Instantiate as atomic_pointer<void*> +// for void* pointer types. +template<typename T> +class atomic_pointer; + +// Atomic bool class. +class atomic_bool; + +// Atomic int32 class +class atomic_int32_t; + +// Atomic int64 class. +class atomic_int64_t; + +// Atomic float class. +class atomic_float; + +// Atomic double class. +class atomic_double; + +/////////////////////////////////////////////////////////////////////////////// +// Class hiearchy. +/////////////////////////////////////////////////////////////////////////////// + +// Base functionality for atomic types. Defines exchange(), load(), +// store(), compare_exhange_weak(), compare_exchange_strong() +template <typename T> +class atomic; + +// Subtype of atomic<T> for numbers likes float and integer types but not bool. +// Adds fetch_add() and fetch_sub(). +template <typename T> +class atomic_number; + +// Subtype of atomic_number<T> for integer types like int32 and int64. Adds +// increment and decrement. +template <typename T> +class atomic_integral; + +/////////////////////////////////////////////////////////////////////////////// +// Implimentation. +/////////////////////////////////////////////////////////////////////////////// + +// Similar to C++11 std::atomic<T>. +// atomic<T> may be instantiated with any TriviallyCopyable type T. +// atomic<T> is neither copyable nor movable. +template <typename T> +class atomic { + public: + typedef T ValueType; + + // C++11 forbids a copy constructor for std::atomic<T>, it also forbids + // a move operation. + atomic() : value_() {} + explicit atomic(T v) : value_(v) {} + + // Checks whether the atomic operations on all objects of this type + // are lock-free. + // Returns true if the atomic operations on the objects of this type + // are lock-free, false otherwise. + // + // All atomic types may be implemented using mutexes or other locking + // operations, rather than using the lock-free atomic CPU instructions. + // atomic types are also allowed to be sometimes lock-free, e.g. if only + // aligned memory accesses are naturally atomic on a given architecture, + // misaligned objects of the same type have to use locks. + // + // See also std::atomic<T>::is_lock_free(). + bool is_lock_free() const { return false; } + bool is_lock_free() const volatile { return false; } + + // Atomically replaces the value of the atomic object + // and returns the value held previously. + // See also std::atomic<T>::exchange(). + T exchange(T new_val) { + T old_value; + { + starboard::ScopedLock lock(mutex_); + old_value = value_; + value_ = new_val; + } + return old_value; + } + + // Atomically obtains the value of the atomic object. + // See also std::atomic<T>::load(). + T load() const { + starboard::ScopedLock lock(mutex_); + return value_; + } + + // Stores the value. See std::atomic<T>::store(...) + void store(T val) { + starboard::ScopedLock lock(mutex_); + value_ = val; + } + + // compare_exchange_strong(...) sets the new value if and only if + // *expected_value matches what is stored internally. + // If this succeeds then true is returned and *expected_value == new_value. + // Otherwise If there is a mismatch then false is returned and + // *expected_value is set to the internal value. + // Inputs: + // new_value: Attempt to set the value to this new value. + // expected_value: A test condition for success. If the actual value + // matches the expected_value then the swap will succeed. + // + // See also std::atomic<T>::compare_exchange_strong(...). + bool compare_exchange_strong(T* expected_value, T new_value) { + // Save original value so that its local. This hints to the compiler + // that test_val doesn't have aliasing issues and should result in + // more optimal code inside of the lock. + const T test_val = *expected_value; + starboard::ScopedLock lock(mutex_); + if (test_val == value_) { + value_ = new_value; + return true; + } else { + *expected_value = value_; + return false; + } + } + + // Weak version of this function is documented to be faster, but has allows + // weaker memory ordering and therefore will sometimes have a false negative: + // The value compared will actually be equal but the return value from this + // function indicates otherwise. + // By default, the function delegates to compare_exchange_strong(...). + // + // See also std::atomic<T>::compare_exchange_weak(...). + bool compare_exchange_weak(T* expected_value, T new_value) { + return compare_exchange_strong(expected_value, new_value); + } + + protected: + T value_; + starboard::Mutex mutex_; +}; + +// A subclass of atomic<T> that adds fetch_add(...) and fetch_sub(...). +template <typename T> +class atomic_number : public atomic<T> { + public: + typedef atomic<T> Super; + typedef T ValueType; + + atomic_number() : Super() {} + explicit atomic_number(T v) : Super(v) {} + + // Returns the previous value before the input value was added. + // See also std::atomic<T>::fetch_add(...). + T fetch_add(T val) { + T old_val; + { + starboard::ScopedLock lock(this->mutex_); + old_val = this->value_; + this->value_ += val; + } + return old_val; + } + + // Returns the value before the operation was applied. + // See also std::atomic<T>::fetch_sub(...). + T fetch_sub(T val) { + T old_val; + { + starboard::ScopedLock lock(this->mutex_); + old_val = this->value_; + this->value_ -= val; + } + return old_val; + } +}; + +// A subclass to classify Atomic Integers. Adds increment and decrement +// functions. +template <typename T> +class atomic_integral : public atomic_number<T> { + public: + typedef atomic_number<T> Super; + + atomic_integral() : Super() {} + explicit atomic_integral(T v) : Super(v) {} + + T increment() { return this->fetch_add(T(1)); } + T decrement() { return this->fetch_sub(T(1)); } +}; + +// atomic_pointer class. +template <typename T> +class atomic_pointer : public atomic<T> { + public: + typedef atomic<T> Super; + atomic_pointer() : Super() {} + explicit atomic_pointer(T initial_val) : Super(initial_val) {} +}; + +// Simple atomic bool class. This could be optimized for speed using +// compiler intrinsics for concurrent integer modification. +class atomic_bool : public atomic<bool> { + public: + typedef atomic<bool> Super; + atomic_bool() : Super() {} + explicit atomic_bool(bool initial_val) : Super(initial_val) {} +}; + +// Simple atomic int class. This could be optimized for speed using +// compiler intrinsics for concurrent integer modification. +class atomic_int32_t : public atomic_integral<int32_t> { + public: + typedef atomic_integral<int32_t> Super; + atomic_int32_t() : Super() {} + explicit atomic_int32_t(int32_t initial_val) : Super(initial_val) {} +}; + +// Simple atomic int class. This could be optimized for speed using +// compiler intrinsics for concurrent integer modification. +class atomic_int64_t : public atomic_integral<int64_t> { + public: + typedef atomic_integral<int64_t> Super; + atomic_int64_t() : Super() {} + explicit atomic_int64_t(int64_t initial_val) : Super(initial_val) {} +}; + +class atomic_float : public atomic_number<float> { + public: + typedef atomic_number<float> Super; + atomic_float() : Super() {} + explicit atomic_float(float initial_val) : Super(initial_val) {} +}; + +class atomic_double : public atomic_number<double> { + public: + typedef atomic_number<double> Super; + atomic_double() : Super() {} + explicit atomic_double(double initial_val) : Super(initial_val) {} +}; + +} // namespace nb + +#endif // NB_ATOMIC_H_
diff --git a/src/nb/atomic_test.cc b/src/nb/atomic_test.cc new file mode 100644 index 0000000..fc76703 --- /dev/null +++ b/src/nb/atomic_test.cc
@@ -0,0 +1,317 @@ +/* + * Copyright 2016 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 "nb/atomic.h" + +#include <algorithm> +#include <vector> + +#include "nb/test_thread.h" +#include "starboard/configuration.h" +#include "starboard/mutex.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace nb { +namespace { + +/////////////////////////////////////////////////////////////////////////////// +// Boilerplate for test setup. +/////////////////////////////////////////////////////////////////////////////// + +// Defines a typelist for all atomic types. +typedef ::testing::Types<atomic_int32_t, atomic_int64_t, + atomic_float, atomic_double, + atomic_bool, + atomic_pointer<int*> > AllAtomicTypes; + +// Defines a typelist for just atomic number types. +typedef ::testing::Types<atomic_int32_t, atomic_int64_t, + atomic_float, atomic_double> AtomicNumberTypes; + +// Defines test type that will be instantiated using each type in +// AllAtomicTypes type list. +template <typename T> +class AtomicTest : public ::testing::Test {}; +TYPED_TEST_CASE(AtomicTest, AllAtomicTypes); // Registration. + +// Defines test type that will be instantiated using each type in +// AtomicNumberTypes type list. +template <typename T> +class AtomicNumberTest : public ::testing::Test {}; +TYPED_TEST_CASE(AtomicNumberTest, AtomicNumberTypes); // Registration. + + +/////////////////////////////////////////////////////////////////////////////// +// Singlethreaded tests. +/////////////////////////////////////////////////////////////////////////////// + +// Tests default constructor and single-argument constructor. +TYPED_TEST(AtomicTest, Constructors) { + typedef TypeParam AtomicT; + typedef typename AtomicT::ValueType T; + + const T zero(0); + const T one = zero + 1; // Allows AtomicPointer<T*>. + + AtomicT atomic_default; + + // Tests that default value is zero. + ASSERT_EQ(atomic_default.load(), zero); + AtomicT atomic_val(one); + ASSERT_EQ(one, atomic_val.load()); +} + +// Tests load() and exchange(). +TYPED_TEST(AtomicTest, Load_Exchange_SingleThread) { + typedef TypeParam AtomicT; + typedef typename AtomicT::ValueType T; + + const T zero(0); + const T one = zero + 1; // Allows AtomicPointer<T*>. + + AtomicT atomic; + ASSERT_EQ(atomic.load(), zero); // Default is 0. + ASSERT_EQ(zero, atomic.exchange(one)); // Old value was 0. + + // Tests that AtomicType has const get function. + const AtomicT& const_atomic = atomic; + ASSERT_EQ(one, const_atomic.load()); +} + +// Tests compare_exchange_strong(). +TYPED_TEST(AtomicNumberTest, CompareExchangeStrong_SingleThread) { + typedef TypeParam AtomicT; + typedef typename AtomicT::ValueType T; + + const T zero(0); + const T one = zero + 1; // Allows AtomicPointer<T*>. + + AtomicT atomic; + ASSERT_EQ(atomic.load(), zero); // Default is 0. + T expected_value = zero; + // Should succeed. + ASSERT_TRUE(atomic.compare_exchange_strong(&expected_value, + one)); // New value. + + ASSERT_EQ(zero, expected_value); + ASSERT_EQ(one, atomic.load()); // Expect that value was set. + + expected_value = zero; + // Asserts that when the expected and actual value is mismatched that the + // compare_exchange_strong() fails. + ASSERT_FALSE(atomic.compare_exchange_strong(&expected_value, // Mismatched. + zero)); // New value. + + // Failed and this means that expected_value should be set to what the + // internal value was. In this case, one. + ASSERT_EQ(expected_value, one); + ASSERT_EQ(one, atomic.load()); + + ASSERT_TRUE(atomic.compare_exchange_strong(&expected_value, // Matches. + zero)); + ASSERT_EQ(expected_value, one); +} + +// Tests atomic fetching and adding. +TYPED_TEST(AtomicNumberTest, FetchAdd_SingleThread) { + typedef TypeParam AtomicT; + typedef typename AtomicT::ValueType T; + + const T zero(0); + const T one = zero + 1; // Allows atomic_pointer<T*>. + const T two = zero + 2; + + AtomicT atomic; + ASSERT_EQ(atomic.load(), zero); // Default is 0. + ASSERT_EQ(zero, atomic.fetch_add(one)); // Prev value was 0. + ASSERT_EQ(one, atomic.load()); // Now value is this. + ASSERT_EQ(one, atomic.fetch_add(one)); // Prev value was 1. + ASSERT_EQ(two, atomic.exchange(one)); // Old value was 2. +} + +// Tests atomic fetching and subtracting. +TYPED_TEST(AtomicNumberTest, FetchSub_SingleThread) { + typedef TypeParam AtomicT; + typedef typename AtomicT::ValueType T; + + const T zero(0); + const T one = zero + 1; // Allows AtomicPointer<T*>. + const T two = zero + 2; + const T neg_two(zero-2); + + AtomicT atomic; + ASSERT_EQ(atomic.load(), zero); // Default is 0. + atomic.exchange(two); + ASSERT_EQ(two, atomic.fetch_sub(one)); // Prev value was 2. + ASSERT_EQ(one, atomic.load()); // New value. + ASSERT_EQ(one, atomic.fetch_sub(one)); // Prev value was one. + ASSERT_EQ(zero, atomic.load()); // New 0. + ASSERT_EQ(zero, atomic.fetch_sub(two)); + ASSERT_EQ(neg_two, atomic.load()); // 0-2 = -2 +} + +/////////////////////////////////////////////////////////////////////////////// +// Multithreaded tests. +/////////////////////////////////////////////////////////////////////////////// + +// A thread that will execute compare_exhange_strong() and write out a result +// to a shared output. +template <typename AtomicT> +class CompareExchangeThread : public TestThread { + public: + typedef typename AtomicT::ValueType T; + CompareExchangeThread(int start_num, + int end_num, + AtomicT* atomic_value, + std::vector<T>* output, + starboard::Mutex* output_mutex) + : start_num_(start_num), end_num_(end_num), + atomic_value_(atomic_value), output_(output), + output_mutex_(output_mutex) { + } + + virtual void Run() { + std::vector<T> output_buffer; + output_buffer.reserve(end_num_ - start_num_); + for (int i = start_num_; i < end_num_; ++i) { + T new_value = T(i); + while (true) { + if (std::rand() % 3 == 0) { + // 1 in 3 chance of yeilding. + // Attempt to cause more contention by giving other threads a chance + // to run. + SbThreadYield(); + } + + const T prev_value = atomic_value_->load(); + T expected_value = prev_value; + const bool success = + atomic_value_->compare_exchange_strong(&expected_value, + new_value); + if (success) { + output_buffer.push_back(prev_value); + break; + } + } + } + + // Lock the output to append this output buffer. + starboard::ScopedLock lock(*output_mutex_); + output_->insert(output_->end(), + output_buffer.begin(), + output_buffer.end()); + } + private: + const int start_num_; + const int end_num_; + AtomicT*const atomic_value_; + std::vector<T>*const output_; + starboard::Mutex*const output_mutex_; +}; + +// Tests Atomic<T>::compare_exchange_strong(). Each thread has a unique +// sequential range [0,1,2,3 ... ), [5,6,8, ...) that it will generate. +// The numbers are sequentially written to the shared Atomic type and then +// exposed to other threads: +// +// Generates [0,1,2,...,n/2) +// +------+ Thread A <--------+ (Write Exchanged Value) +// | | +// | compare_exchange() +---> exchanged? ---+ +// +----> +------------+ +----+ v +// | AtomicType | Output vector +// +----> +------------+ +----+ ^ +// | compare_exchange() +---> exchanged? ---+ +// | | +// +------+ Thread B <--------+ +// Generates [n/2,n/2+1,...,n) +// +// By repeatedly calling atomic<T>::compare_exchange_strong() by each of the +// threads, each will see the previous value of the shared variable when their +// exchange (atomic swap) operation is successful. If all of the swapped out +// values are recombined then it will form the original generated sequence from +// all threads. +// +// TEST PHASE +// sort( output vector ) AND TEST THAT +// output vector Contains [0,1,2,...,n) +// +// The test passes when the output array is tested that it contains every +// expected generated number from all threads. If compare_exchange_strong() is +// written incorrectly for an atomic type then the output array will have +// duplicates or otherwise not be equal to the expected natural number set. +TYPED_TEST(AtomicNumberTest, Test_CompareExchange_MultiThreaded) { + typedef TypeParam AtomicT; + typedef typename AtomicT::ValueType T; + + static const int kNumElements = 1000; + static const int kNumThreads = 4; + + AtomicT atomic_value(T(-1)); + std::vector<TestThread*> threads; + std::vector<T> output_values; + starboard::Mutex output_mutex; + + for (int i = 0; i < kNumThreads; ++i) { + const int start_num = (kNumElements * i) / kNumThreads; + const int end_num = (kNumElements * (i + 1)) / kNumThreads; + threads.push_back( + new CompareExchangeThread<AtomicT>( + start_num, // defines the number range to generate. + end_num, + &atomic_value, + &output_values, + &output_mutex)); + } + + // These threads will generate unique numbers in their range and then + // write them to the output array. + for (int i = 0; i < kNumThreads; ++i) { + threads[i]->Start(); + } + + for (int i = 0; i < kNumThreads; ++i) { + threads[i]->Join(); + } + // Cleanup threads. + for (int i = 0; i < kNumThreads; ++i) { + delete threads[i]; + } + threads.clear(); + + // Final value needs to be written out. The last thread to join doesn't + // know it's the last and therefore the final value in the shared + // has not be pushed to the output array. + output_values.push_back(atomic_value.load()); + std::sort(output_values.begin(), output_values.end()); + + // We expect the -1 value because it was the explicit initial value of the + // shared atomic. + ASSERT_EQ(T(-1), output_values[0]); + ASSERT_EQ(T(0), output_values[1]); + output_values.erase(output_values.begin()); // Chop off the -1 at the front. + + // Finally, assert that the output array is equal to the natural numbers + // after it has been sorted. + ASSERT_EQ(output_values.size(), kNumElements); + // All of the elements should be equal too. + for (int i = 0; i < output_values.size(); ++i) { + ASSERT_EQ(output_values[i], T(i)); + } +} + +} // namespace +} // namespace nb
diff --git a/src/nb/fixed_no_free_allocator.cc b/src/nb/fixed_no_free_allocator.cc index 3722da6..53a615f 100644 --- a/src/nb/fixed_no_free_allocator.cc +++ b/src/nb/fixed_no_free_allocator.cc
@@ -27,28 +27,6 @@ next_memory_ = memory_start_; } -void* FixedNoFreeAllocator::Allocate(std::size_t size, - std::size_t alignment, - bool align_pointer) { - // Find the next aligned memory available. - uint8_t* aligned_next_memory = - AsPointer(AlignUp(AsInteger(next_memory_), alignment)); - - if (aligned_next_memory + size < aligned_next_memory) { - // "aligned_next_memory + size" overflows. - return NULL; - } - - if (aligned_next_memory + size > memory_end_) { - // We don't have enough memory available to make this allocation. - return NULL; - } - - void* memory_pointer = align_pointer ? aligned_next_memory : next_memory_; - next_memory_ = aligned_next_memory + size; - return memory_pointer; -} - void FixedNoFreeAllocator::Free(void* memory) { // Nothing to do here besides ensure that the freed memory belongs to us. SB_DCHECK(memory >= memory_start_); @@ -67,4 +45,30 @@ SB_NOTIMPLEMENTED(); } +void* FixedNoFreeAllocator::Allocate(std::size_t* size, + std::size_t alignment, + bool align_pointer) { + // Find the next aligned memory available. + uint8_t* aligned_next_memory = + AsPointer(AlignUp(AsInteger(next_memory_), alignment)); + + if (aligned_next_memory + *size < aligned_next_memory) { + // "aligned_next_memory + size" overflows. + return NULL; + } + + if (aligned_next_memory + *size > memory_end_) { + // We don't have enough memory available to make this allocation. + return NULL; + } + + if (!align_pointer) { + *size += AsInteger(aligned_next_memory) - AsInteger(next_memory_); + } + + void* memory_pointer = align_pointer ? aligned_next_memory : next_memory_; + next_memory_ = aligned_next_memory + *size; + return memory_pointer; +} + } // namespace nb
diff --git a/src/nb/fixed_no_free_allocator.h b/src/nb/fixed_no_free_allocator.h index 696a7c0..31bfc60 100644 --- a/src/nb/fixed_no_free_allocator.h +++ b/src/nb/fixed_no_free_allocator.h
@@ -37,22 +37,23 @@ class FixedNoFreeAllocator : public Allocator { public: FixedNoFreeAllocator(void* memory_start, std::size_t memory_size); - void* Allocate(std::size_t size) { return Allocate(size, 1, true); } + void* Allocate(std::size_t size) { return Allocate(&size, 1, true); } void* Allocate(std::size_t size, std::size_t alignment) { - return Allocate(size, alignment, true); + return Allocate(&size, alignment, true); } - void* AllocateForAlignment(std::size_t size, std::size_t alignment) { + void* AllocateForAlignment(std::size_t* size, std::size_t alignment) { return Allocate(size, alignment, false); } - void* Allocate(std::size_t size, std::size_t alignment, bool align_pointer); void Free(void* memory); std::size_t GetCapacity() const; std::size_t GetAllocated() const; void PrintAllocations() const; private: + void* Allocate(std::size_t* size, std::size_t alignment, bool align_pointer); + // The start of our memory range, as passed in to the constructor. void* const memory_start_;
diff --git a/src/nb/hash.cc b/src/nb/hash.cc new file mode 100644 index 0000000..b04c7d5 --- /dev/null +++ b/src/nb/hash.cc
@@ -0,0 +1,32 @@ +/* + * Copyright 2016 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 "nb/hash.h" + +#include "starboard/types.h" + +namespace nb { + +namespace { +// Injects the super fast hash into the nb namespace. +#include "third_party/super_fast_hash/super_fast_hash.cc" +} // namespace + +uint32_t RuntimeHash32(const char* data, int length, uint32_t prev_hash) { + return SuperFastHash(data, length, prev_hash); +} + +} // namespace nb
diff --git a/src/nb/hash.h b/src/nb/hash.h new file mode 100644 index 0000000..fe48e3d --- /dev/null +++ b/src/nb/hash.h
@@ -0,0 +1,37 @@ +/* + * Copyright 2016 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 NB_HASH_H_ +#define NB_HASH_H_ + +#include "starboard/types.h" + +namespace nb { + +enum { kDefaultSeed = 0x12345789 }; + +// RuntimeHash32 is a 32 bit hash for data. The only guarantee is that this +// hash is persistent for the lifetime of the program. This hash function +// is not guaranteed to be consistent across platforms. The hash value should +// never be saved to disk. +// It's sufficient however, to use as an in-memory hashtable. +uint32_t RuntimeHash32(const char* data, + int length, + uint32_t prev_hash = kDefaultSeed); + +} // namespace nb + +#endif // NB_HASH_H_
diff --git a/src/nb/memory_pool.cc b/src/nb/memory_pool.cc index 840042c..4ae9a7e 100644 --- a/src/nb/memory_pool.cc +++ b/src/nb/memory_pool.cc
@@ -23,15 +23,21 @@ MemoryPool::MemoryPool(void* buffer, std::size_t size, bool thread_safe, - bool verify_full_capacity) + bool verify_full_capacity, + std::size_t small_allocation_threshold) : no_free_allocator_(buffer, size), reuse_allocator_( - scoped_ptr<Allocator>(new ReuseAllocator(&no_free_allocator_)), + scoped_ptr<Allocator>(new ReuseAllocator(&no_free_allocator_, + size, + small_allocation_threshold)), thread_safe) { SB_DCHECK(buffer); SB_DCHECK(size != 0U); - if (verify_full_capacity) { + // This is redundant if ReuseAllocator::Allcator() can allocate the difference + // between the requested size and the last free block from the fallback + // allocator and combine the blocks. + if (verify_full_capacity && small_allocation_threshold == 0) { void* p = Allocate(size); SB_DCHECK(p); Free(p);
diff --git a/src/nb/memory_pool.h b/src/nb/memory_pool.h index cef03db..b81e893 100644 --- a/src/nb/memory_pool.h +++ b/src/nb/memory_pool.h
@@ -29,30 +29,16 @@ // as necessary. class MemoryPool : public Allocator { public: - // When |verify_full_capacity| is true, the ctor tries to allocate the whole - // budget and free it immediately. This can: - // 1. Ensure the |size| is accurate after accounting for all implicit - // alignment enforced by the underlying allocators. - // 2. The |reuse_allocator_| contains a free block of the whole budget. As - // the |reuse_allocator_| doesn't support extending of free block, an - // allocation that is larger than the both biggest free block in the - // |reuse_allocator_| and the remaining memory inside the - // |no_free_allocator_| will fail even if the combination of both can - // fulfill the allocation. - // Note that when |verify_full_capacity| is true, GetHighWaterMark() always - // return the budget, which makes memory usage tracking useless. MemoryPool(void* buffer, std::size_t size, bool thread_safe, - bool verify_full_capacity = false); + bool verify_full_capacity = false, + std::size_t small_allocation_threshold = 0); void* Allocate(std::size_t size) { return reuse_allocator_.Allocate(size); } void* Allocate(std::size_t size, std::size_t alignment) { return reuse_allocator_.Allocate(size, alignment); } - void* AllocateForAlignment(std::size_t size, std::size_t alignment) { - return reuse_allocator_.AllocateForAlignment(size, alignment); - } void Free(void* memory) { reuse_allocator_.Free(memory); } std::size_t GetCapacity() const { return reuse_allocator_.GetCapacity(); } std::size_t GetAllocated() const { return reuse_allocator_.GetAllocated(); }
diff --git a/src/nb/memory_scope.cc b/src/nb/memory_scope.cc new file mode 100644 index 0000000..f475a16 --- /dev/null +++ b/src/nb/memory_scope.cc
@@ -0,0 +1,59 @@ +/* + * Copyright 2016 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 "nb/memory_scope.h" +#include "starboard/log.h" +#include "starboard/atomic.h" + +NbMemoryScopeReporter* s_memory_reporter_ = NULL; + +bool NbSetMemoryScopeReporter(NbMemoryScopeReporter* reporter) { + // Flush all the pending memory writes out to main memory so that + // other threads see a fully constructed reporter. + SbAtomicMemoryBarrier(); + s_memory_reporter_ = reporter; +#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + SbLogRaw("\nMemory Scope Reporting is disabled because this build does " + "not support it. Try a QA, devel or debug build.\n"); + return false; +#else + return true; +#endif +} + +void NbPushMemoryScope(NbMemoryScopeInfo* memory_scope_info) { +#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + return; +#else + if (SB_LIKELY(!s_memory_reporter_)) { + return; + } + s_memory_reporter_->push_memory_scope_cb( + s_memory_reporter_->context, + memory_scope_info); +#endif +} + +void NbPopMemoryScope() { +#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + return; +#else + if (SB_LIKELY(!s_memory_reporter_)) { + return; + } + s_memory_reporter_->pop_memory_scope_cb(s_memory_reporter_->context); +#endif +}
diff --git a/src/nb/memory_scope.h b/src/nb/memory_scope.h new file mode 100644 index 0000000..72e1ddc --- /dev/null +++ b/src/nb/memory_scope.h
@@ -0,0 +1,143 @@ +/* + * Copyright 2016 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 NB_MEMORY_SCOPE_H_ +#define NB_MEMORY_SCOPE_H_ + +/////////////////////////////////////////////////////////////////////////////// +// Macros to define the memory scope objects. These are objects that are used +// to annotate sections of the code base as belonging to a particular memory +// scope. Note that this is an annotation and does not memory allocation. +/////////////////////////////////////////////////////////////////////////////// + +// Macro to track the memory scope inside a function or block of code. The +// memory scope is in effect until the end of the code block. +// Example: +// void Foo() { +// TRACK_MEMORY_SCOPE("FooMemoryScope"); +// // pops the memory scope at the end. +// } + +#if !defined(__cplusplus) +// Disallow macro use for non-C++ builds. +#define TRACK_MEMORY_SCOPE(STR) error_forbidden_in_non_c_plus_plus_code +#define TRACK_MEMORY_SCOPE_DYNAMIC(STR) error_forbidden_in_non_c_plus_plus_code +#elif defined(STARBOARD_ALLOWS_MEMORY_TRACKING) +#define TRACK_MEMORY_SCOPE(STR) TRACK_MEMORY_STATIC_CACHED(STR) +#define TRACK_MEMORY_SCOPE_DYNAMIC(STR) TRACK_MEMORY_STATIC_NOT_CACHED(STR) +#else +// No-op when starboard does not allow memory tracking. +#define TRACK_MEMORY_SCOPE(STR) +#define TRACK_MEMORY_SCOPE_DYNAMIC(STR) +#endif + +// Preprocessor needs double expansion in order to __FILE__, __LINE__ and +// __FUNCTION__ properly. +#define TRACK_MEMORY_STATIC_CACHED(STR) \ + TRACK_MEMORY_STATIC_CACHED_IMPL_2(STR, __FILE__, __LINE__, __FUNCTION__) + +#define TRACK_MEMORY_STATIC_NOT_CACHED(STR) \ + TRACK_MEMORY_STATIC_NOT_CACHED_IMPL_2(STR, __FILE__, __LINE__, __FUNCTION__) + +// Only enable TRACK_MEMORY_STATIC_CACHED_IMPL_2 if starboard allows memory +// tracking. +#define TRACK_MEMORY_STATIC_CACHED_IMPL_2(Str, FileStr, LineNum, FuncStr) \ + static NbMemoryScopeInfo memory_scope_handle_##LineNum = \ + { NULL, Str, FileStr, LineNum, FuncStr, true }; \ + NbPushMemoryScope(&memory_scope_handle_##LineNum); \ + NbPopMemoryScopeOnScopeEnd pop_on_scope_end_##LineNum; + +#define TRACK_MEMORY_STATIC_NOT_CACHED_IMPL_2(Str, FileStr, LineNum, FuncStr) \ + NbMemoryScopeInfo memory_scope_handle_##LineNum = { \ + NULL, Str, FileStr, LineNum, FuncStr, false}; \ + NbPushMemoryScope(&memory_scope_handle_##LineNum); \ + NbPopMemoryScopeOnScopeEnd pop_on_scope_end_##LineNum; + +#ifdef __cplusplus +extern "C" { +#endif + +struct NbMemoryScopeReporter; +struct NbMemoryScopeInfo; + +// Sets the memory reporter. Returns true on success, false something +// goes wrong. +bool NbSetMemoryScopeReporter(NbMemoryScopeReporter* reporter); + +// Note that we pass by pointer because the memory scope contains a +// variable allowing the result to be cached. +void NbPushMemoryScope(NbMemoryScopeInfo* memory_scope); +void NbPopMemoryScope(); + +/////////////////////////////////////////////////////////////////////////////// +// Implementation +/////////////////////////////////////////////////////////////////////////////// +// Interface for handling memory scopes. +typedef void (*NbReportPushMemoryScopeCallback)(void* context, + NbMemoryScopeInfo* info); +typedef void (*NbReportPopMemoryScopeCallback)(void* context); + +struct NbMemoryScopeReporter { + // Callback to report pushing of memory scope. + NbReportPushMemoryScopeCallback push_memory_scope_cb; + + // Callback to report poping of the memory scope. + NbReportPopMemoryScopeCallback pop_memory_scope_cb; + + // Optional, is passed to the callbacks as first argument. + void* context; +}; + +// This MemoryScope must remain a POD data type so that it can be statically +// initialized. +struct NbMemoryScopeInfo { + // cached_handle_ allows a cached result of the the fields represented in + // this struct to be generated and the handle be placed into this field. + // See also allows_caching_. + void* cached_handle_; + + // Represents the name of the memory scope. I.E. "Javascript" or "Gfx". + const char* memory_scope_name_; + + // Represents the file name that this memory scope was created at. + const char* file_name_; + + // Represents the line number that this memory scope was created at. + int line_number_; + + // Represents the function name that this memory scope was created at. + const char* function_name_; + + // When true, if cached_handle_ is 0 then an object may be created that + // represents the fields of this object. The handle that represents this + // cached object is then placed in cached_hanlde_. + bool allows_caching_; +}; + +// NbPopMemoryScopeOnScopeEnd is only allowed for C++ builds. +#ifdef __cplusplus +// A helper that pops the memory scope at the end of the current code block. +struct NbPopMemoryScopeOnScopeEnd { + ~NbPopMemoryScopeOnScopeEnd() { NbPopMemoryScope(); } +}; +#endif + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // NB_MEMORY_SCOPE_H_
diff --git a/src/nb/memory_scope_test.cc b/src/nb/memory_scope_test.cc new file mode 100644 index 0000000..e734c2f --- /dev/null +++ b/src/nb/memory_scope_test.cc
@@ -0,0 +1,257 @@ +/* + * Copyright 2016 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 "nb/memory_scope.h" +#include "starboard/mutex.h" +#include "nb/thread_local_object.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace nb { +namespace { + +bool StarboardAllowsMemoryTracking() { +#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + return true; +#else + return false; +#endif +} + +// This is a memory scope reporter that is compatible +// with the MemoryScopeReporter. +class TestMemoryScopeReporter { + public: + typedef std::vector<NbMemoryScopeInfo*> MemoryScopeVector; + + TestMemoryScopeReporter() { + memory_scope_reporter_ = CreateMemoryScopeReporter(); + } + + NbMemoryScopeReporter* memory_scope_reporter() { + return &memory_scope_reporter_; + } + + MemoryScopeVector* stack_thread_local() { return stack_tlo_.GetOrCreate(); } + + void OnPushMemoryScope(NbMemoryScopeInfo* memory_scope) { + stack_thread_local()->push_back(memory_scope); + } + + void OnPopMemoryScope() { + MemoryScopeVector* stack = stack_thread_local(); + if (!stack->empty()) { + stack->pop_back(); + } else { + ADD_FAILURE_AT(__FILE__, __LINE__) + << " stack was empty and could not be popped."; + } + } + + private: + static void OnPushMemoryScopeCallback(void* context, + NbMemoryScopeInfo* info) { + TestMemoryScopeReporter* t = static_cast<TestMemoryScopeReporter*>(context); + t->OnPushMemoryScope(info); + } + + static void OnPopMemoryScopeCallback(void* context) { + TestMemoryScopeReporter* t = static_cast<TestMemoryScopeReporter*>(context); + t->OnPopMemoryScope(); + } + + NbMemoryScopeReporter CreateMemoryScopeReporter() { + NbMemoryScopeReporter reporter = {OnPushMemoryScopeCallback, + OnPopMemoryScopeCallback, this}; + return reporter; + } + + NbMemoryScopeReporter memory_scope_reporter_; + ThreadLocalObject<MemoryScopeVector> stack_tlo_; +}; + +// A test framework for testing the Pushing & popping memory scopes. +// The key feature here is that reporter is setup on the first test +// instance and torn down after the last test has run. +class MemoryScopeReportingTest : public ::testing::Test { + public: + TestMemoryScopeReporter* test_memory_reporter() { return s_reporter_; } + + bool reporting_enabled() const { return s_reporter_enabled_; } + + protected: + static void SetUpTestCase() { + if (!s_reporter_) { + s_reporter_ = new TestMemoryScopeReporter; + } + s_reporter_enabled_ = + NbSetMemoryScopeReporter(s_reporter_->memory_scope_reporter()); + + EXPECT_EQ(StarboardAllowsMemoryTracking(), s_reporter_enabled_) + << "Expected the memory scope reporter to be enabled whenever " + "starboard memory tracking is allowed."; + } + + static void TearDownTestCase() { + // The reporter itself is not deleted because other threads could + // be traversing through it's data structures. It's better just to leave + // the object alive for the purposes of this unit test and set the pointer + // to NULL. + // This is done in order to make the MemoryScopeReport object lock free. + // This increases performance and reduces complexity of design. + NbSetMemoryScopeReporter(NULL); + } + + // Per test setup. + virtual void SetUp() { + test_memory_reporter()->stack_thread_local()->clear(); + } + + static TestMemoryScopeReporter* s_reporter_; + static bool s_reporter_enabled_; +}; +TestMemoryScopeReporter* MemoryScopeReportingTest::s_reporter_ = NULL; +bool MemoryScopeReportingTest::s_reporter_enabled_; + +/////////////////////////////////////////////////////////////////////////////// +// TESTS. +// There are two sets of tests: POSITIVE and NEGATIVE. +// The positive tests are active when STARBOARD_ALLOWS_MEMORY_TRACKING is +// defined and test that memory tracking is enabled. +// NEGATIVE tests ensure that tracking is disabled when +// STARBOARD_ALLOWS_MEMORY_TRACKING is not defined. +// When adding new tests: +// POSITIVE tests are named normally. +// NEGATIVE tests are named with "No" prefixed to the beginning. +// Example: +// TEST_F(MemoryScopeReportingTest, PushPop) <--- POSITIVE test. +// TEST_F(MemoryScopeReportingTest, NoPushPop) <- NEGATIVE test. +// All positive & negative tests are grouped together. +/////////////////////////////////////////////////////////////////////////////// + +#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING) +// These are POSITIVE tests, which test the expectation that when the define +// STARBOARD_ALLOWS_MEMORY_TRACKING is active that the memory scope reporter +// will receive memory scope notifications. + +// Tests the assumption that the SbMemoryAllocate and SbMemoryDeallocate +// will report memory allocations. +TEST_F(MemoryScopeReportingTest, PushPop) { + ASSERT_TRUE(reporting_enabled()); + const int line_number = __LINE__; + const char* file_name = __FILE__; + const char* function_name = __FUNCTION__; + NbMemoryScopeInfo info = {0, // Cached value (null). + "Javascript", // Name of the memory scope. + file_name, // Filename that invoked this. + line_number, // Line number. + function_name, // Function name. + true}; // true allows caching. + + NbPushMemoryScope(&info); + + ASSERT_FALSE(test_memory_reporter()->stack_thread_local()->empty()); + NbMemoryScopeInfo* info_ptr = + test_memory_reporter()->stack_thread_local()->front(); + + EXPECT_EQ(&info, info_ptr); + EXPECT_STREQ(info.file_name_, file_name); + EXPECT_STREQ(info.function_name_, function_name); + EXPECT_EQ(info.line_number_, line_number); + + NbPopMemoryScope(); + EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty()); +} + +// Tests the expectation that the memory reporting macros will +// push/pop memory regions and will also correctly bind to the +// file, linenumber and also the function name. +TEST_F(MemoryScopeReportingTest, Macros) { + ASSERT_TRUE(reporting_enabled()); + // There should be no leftover stack objects. + EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty()); + { + const int line_before = __LINE__; + TRACK_MEMORY_SCOPE("TestMemoryScope"); + const int predicted_line = line_before + 1; + + NbMemoryScopeInfo* info_ptr = + test_memory_reporter()->stack_thread_local()->front(); + + // TRACK_MEMORY_SCOPE is defined to allow caching. + EXPECT_EQ(true, info_ptr->allows_caching_); + + // The cached_handle_ is not mutated by TestMemoryScopeReporter so + // therefore it should be the default value of 0. + EXPECT_EQ(0, info_ptr->cached_handle_); + + EXPECT_STREQ("TestMemoryScope", info_ptr->memory_scope_name_); + EXPECT_STREQ(__FILE__, info_ptr->file_name_); + EXPECT_EQ(predicted_line, info_ptr->line_number_); + EXPECT_STREQ(__FUNCTION__, info_ptr->function_name_); + } + // Expect that the stack object is now empty again. + EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty()); +} + +#else // !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) +// These are NEGATIVE tests, which test the expectation that when the +// STARBOARD_ALLOWS_MEMORY_TRACKING is undefined that the memory scope reprter +// does not receive memory scope notifications. + +// Tests the expectation that push pop does not send notifications to the +// reporter when disabled. +TEST_F(MemoryScopeReportingTest, NoPushPop) { + ASSERT_FALSE(reporting_enabled()); + const int line_number = __LINE__; + const char* file_name = __FILE__; + const char* function_name = __FUNCTION__; + NbMemoryScopeInfo info = {0, // Cached value (null). + "Javascript", // Name of the memory scope. + file_name, // Filename that invoked this. + line_number, // Line number. + function_name, // Function name. + true}; // true allows caching. + + NbPushMemoryScope(&info); + + ASSERT_FALSE(test_memory_reporter()->stack_thread_local()->empty()); + NbMemoryScopeInfo* info_ptr = + test_memory_reporter()->stack_thread_local()->front(); + + EXPECT_EQ(&info, info_ptr); + EXPECT_STREQ(info.file_name_, file_name); + EXPECT_STREQ(info.function_name_, function_name); + EXPECT_EQ(info.line_number_, line_number); + + NbPopMemoryScope(); + EXPECT_TRUE(test_memory_reporter()->stack_thread_local()->empty()); +} + +// Tests the expectation that the memory reporting macros are disabled when +// memory tracking is not allowed. +TEST_F(MemoryScopeReportingTest, NoMacros) { + ASSERT_FALSE(reporting_enabled()); + // Test that the macros do nothing when memory reporting has been + // disabled. + TRACK_MEMORY_SCOPE("InternalMemoryRegion"); + ASSERT_TRUE(test_memory_reporter()->stack_thread_local()->empty()) + << "Memory reporting received notifications when it should be disabled."; +} +#endif + +} // namespace. +} // namespace nb.
diff --git a/src/nb/nb.gyp b/src/nb/nb.gyp index 6f56df7..360ac03 100644 --- a/src/nb/nb.gyp +++ b/src/nb/nb.gyp
@@ -20,31 +20,45 @@ 'variables': { 'includes_starboard': 1, }, - 'sources': [ - 'allocator.h', - 'allocator_decorator.cc', - 'allocator_decorator.h', - 'fixed_no_free_allocator.cc', - 'fixed_no_free_allocator.h', - 'memory_pool.cc', - 'memory_pool.h', - 'move.h', - 'pointer_arithmetic.h', - 'rect.h', - 'ref_counted.cc', - 'ref_counted.h', - 'reuse_allocator.cc', - 'reuse_allocator.h', - 'scoped_ptr.h', - 'thread_collision_warner.cc', - 'thread_collision_warner.h', - ], - - 'dependencies': [ - '<(DEPTH)/starboard/starboard.gyp:starboard', - ], - 'conditions': [ + ['OS=="starboard" or (OS=="lb_shell" and target_arch == "ps3")', { + 'sources': [ + 'allocator.h', + 'allocator_decorator.cc', + 'allocator_decorator.h', + 'analytics/memory_tracker.cc', + 'analytics/memory_tracker.h', + 'analytics/memory_tracker_impl.cc', + 'analytics/memory_tracker_impl.h', + 'analytics/memory_tracker_helpers.cc', + 'analytics/memory_tracker_helpers.h', + 'atomic.h', + 'fixed_no_free_allocator.cc', + 'fixed_no_free_allocator.h', + 'hash.cc', + 'hash.h', + 'memory_pool.cc', + 'memory_pool.h', + 'memory_scope.cc', + 'memory_scope.h', + 'move.h', + 'pointer_arithmetic.h', + 'rect.h', + 'ref_counted.cc', + 'ref_counted.h', + 'reuse_allocator.cc', + 'reuse_allocator.h', + 'scoped_ptr.h', + 'simple_thread.cc', + 'simple_thread.h', + 'thread_collision_warner.cc', + 'thread_collision_warner.h', + 'thread_local_object.h', + ], + 'dependencies': [ + '<(DEPTH)/starboard/starboard.gyp:starboard', + ], + }], ['target_arch == "ps4"', { 'sources': [ 'kernel_contiguous_allocator_ps4.cc', @@ -59,15 +73,26 @@ { 'target_name': 'nb_test', 'type': '<(gtest_target_type)', - 'sources': [ - 'fixed_no_free_allocator_test.cc', - 'reuse_allocator_test.cc', - 'run_all_unittests.cc', - ], - 'dependencies': [ - 'nb', - '<(DEPTH)/testing/gmock.gyp:gmock', - '<(DEPTH)/testing/gtest.gyp:gtest', + 'conditions': [ + ['OS=="starboard" or (OS=="lb_shell" and target_arch == "ps3")', { + 'sources': [ + 'analytics/memory_tracker_helpers_test.cc', + 'analytics/memory_tracker_impl_test.cc', + 'analytics/memory_tracker_test.cc', + 'atomic_test.cc', + 'fixed_no_free_allocator_test.cc', + 'memory_scope_test.cc', + 'reuse_allocator_test.cc', + 'run_all_unittests.cc', + 'test_thread.h', + 'thread_local_object_test.cc', + ], + 'dependencies': [ + 'nb', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + }] ], }, { @@ -79,6 +104,7 @@ 'variables': { 'executable_name': 'nb_test', }, + 'includes': [ '../starboard/build/deploy.gypi' ], }, {
diff --git a/src/nb/pointer_arithmetic.h b/src/nb/pointer_arithmetic.h index 411308c..cb1539a 100644 --- a/src/nb/pointer_arithmetic.h +++ b/src/nb/pointer_arithmetic.h
@@ -31,7 +31,7 @@ return reinterpret_cast<uint8_t*>(integer_value); } -// Helper method for subclasses to align addresses up to a specified value. +// Helper method to align addresses up to a specified value. // Returns the the smallest value that is greater than or equal to value, but // aligned to alignment. template <typename T> @@ -42,9 +42,20 @@ template <typename T> T* AlignUp(T* value, uintptr_t alignment) { - uintptr_t decremented_value = AsInteger(value) - 1; - return reinterpret_cast<T*>(decremented_value + alignment - - (decremented_value % alignment)); + return reinterpret_cast<T*>(AlignUp(AsInteger(value), alignment)); +} + +// Helper method to align addresses down to a specified value. +// Returns the the largest value that is less than or equal to value, but +// aligned to alignment. +template <typename T> +T AlignDown(T value, T alignment) { + return value / alignment * alignment; +} + +template <typename T> +T* AlignDown(T* value, uintptr_t alignment) { + return reinterpret_cast<T*>(AlignDown(AsInteger(value), alignment)); } // Helper method for subclasses to determine if a given address or value
diff --git a/src/nb/reuse_allocator.cc b/src/nb/reuse_allocator.cc index 61a40a6..c12375d 100644 --- a/src/nb/reuse_allocator.cc +++ b/src/nb/reuse_allocator.cc
@@ -24,10 +24,104 @@ namespace nb { -ReuseAllocator::ReuseAllocator(Allocator* fallback_allocator) { - fallback_allocator_ = fallback_allocator; - capacity_ = 0; - total_allocated_ = 0; +// Minimum block size to avoid extremely small blocks inside the block list and +// to ensure that a zero sized allocation will return a non-zero sized block. +const std::size_t kMinBlockSizeBytes = 16; + +bool ReuseAllocator::MemoryBlock::Merge(const MemoryBlock& other) { + if (AsInteger(address_) + size_ == AsInteger(other.address_)) { + size_ += other.size_; + return true; + } + if (AsInteger(other.address_) + other.size_ == AsInteger(address_)) { + address_ = other.address_; + size_ += other.size_; + return true; + } + return false; +} + +bool ReuseAllocator::MemoryBlock::CanFullfill(std::size_t request_size, + std::size_t alignment) const { + const std::size_t extra_bytes_for_alignment = + AlignUp(AsInteger(address_), alignment) - AsInteger(address_); + const std::size_t aligned_size = request_size + extra_bytes_for_alignment; + return size_ >= aligned_size; +} + +void ReuseAllocator::MemoryBlock::Allocate(std::size_t request_size, + std::size_t alignment, + bool allocate_from_front, + MemoryBlock* allocated, + MemoryBlock* free) const { + SB_DCHECK(allocated); + SB_DCHECK(free); + SB_DCHECK(CanFullfill(request_size, alignment)); + + // First we assume that the block is just enough to fulfill the allocation and + // leaves no free block. + allocated->address_ = address_; + allocated->size_ = size_; + free->address_ = NULL; + free->size_ = 0; + + if (allocate_from_front) { + // |address_| + // | <- allocated_size -> | <- remaining_size -> | + // ------------------------------------------------------- + // | <- request_size -> | | + // |aligned_address| |end_of_allocation| |address_ + size_| + std::size_t aligned_address = AlignUp(AsInteger(address_), alignment); + std::size_t end_of_allocation = aligned_address + request_size; + std::size_t allocated_size = end_of_allocation - AsInteger(address_); + std::size_t remaining_size = size_ - allocated_size; + if (remaining_size < kMinBlockSizeBytes) { + return; + } + allocated->size_ = allocated_size; + free->address_ = AsPointer(end_of_allocation); + free->size_ = remaining_size; + } else { + // |address_| + // | <- remaining_size -> | <- allocated_size -> | + // ------------------------------------------------------- + // | <- request_size -> | | + // |aligned_address| |address_ + size_| + std::size_t aligned_address = + AlignDown(AsInteger(address_) + size_ - request_size, alignment); + std::size_t allocated_size = AsInteger(address_) + size_ - aligned_address; + std::size_t remaining_size = size_ - allocated_size; + if (remaining_size < kMinBlockSizeBytes) { + return; + } + allocated->address_ = AsPointer(aligned_address); + allocated->size_ = allocated_size; + free->address_ = address_; + free->size_ = remaining_size; + } +} + +ReuseAllocator::ReuseAllocator(Allocator* fallback_allocator) + : fallback_allocator_(fallback_allocator), + small_allocation_threshold_(0), + capacity_(0), + total_allocated_(0) {} + +ReuseAllocator::ReuseAllocator(Allocator* fallback_allocator, + std::size_t capacity, + std::size_t small_allocation_threshold) + : fallback_allocator_(fallback_allocator), + small_allocation_threshold_(small_allocation_threshold), + capacity_(0), + total_allocated_(0) { + // If |small_allocation_threshold_| is non-zero, this class will use last-fit + // strategy to fulfill small allocations. Pre-allocator full capacity so + // last-fit makes sense. + if (small_allocation_threshold_ > 0) { + void* p = Allocate(capacity, 1); + SB_DCHECK(p); + Free(p); + } } ReuseAllocator::~ReuseAllocator() { @@ -44,18 +138,14 @@ } } -void ReuseAllocator::AddFreeBlock(void* address, std::size_t size) { - MemoryBlock new_block; - new_block.address = address; - new_block.size = size; - +ReuseAllocator::FreeBlockSet::iterator ReuseAllocator::AddFreeBlock( + MemoryBlock block_to_add) { if (free_blocks_.size() == 0) { - free_blocks_.insert(new_block); - return; + return free_blocks_.insert(block_to_add).first; } // See if we can merge this block with one on the right or left. - FreeBlockSet::iterator it = free_blocks_.lower_bound(new_block); + FreeBlockSet::iterator it = free_blocks_.lower_bound(block_to_add); // lower_bound will return an iterator to our neighbor on the right, // if one exists. FreeBlockSet::iterator right_to_erase = free_blocks_.end(); @@ -63,9 +153,7 @@ if (it != free_blocks_.end()) { MemoryBlock right_block = *it; - if (AsInteger(new_block.address) + new_block.size == - AsInteger(right_block.address)) { - new_block.size += right_block.size; + if (block_to_add.Merge(right_block)) { right_to_erase = it; } } @@ -75,10 +163,7 @@ it--; MemoryBlock left_block = *it; // Are we contiguous with the block to our left? - if (AsInteger(left_block.address) + left_block.size == - AsInteger(new_block.address)) { - new_block.address = left_block.address; - new_block.size += left_block.size; + if (block_to_add.Merge(left_block)) { left_to_erase = it; } } @@ -90,7 +175,7 @@ free_blocks_.erase(left_to_erase); } - free_blocks_.insert(new_block); + return free_blocks_.insert(block_to_add).first; } void ReuseAllocator::RemoveFreeBlock(FreeBlockSet::iterator it) { @@ -101,11 +186,6 @@ return Allocate(size, 1); } -void* ReuseAllocator::AllocateForAlignment(std::size_t size, - std::size_t alignment) { - return Allocate(size, alignment); -} - void* ReuseAllocator::Allocate(std::size_t size, std::size_t alignment) { if (alignment == 0) { alignment = 1; @@ -113,15 +193,14 @@ // Try to satisfy request from free list. // First look for a block that is appropriately aligned. - // If we can't, look for a block that is big enough that we can - // carve out an aligned block. + // If we can't, look for a block that is big enough that we can carve out an + // aligned block. // If there is no such block, allocate more from our fallback allocator. void* user_address = 0; - // Keeping things rounded and aligned will help us - // avoid creating tiny and/or badly misaligned free blocks. - // Also ensure even for a 0-byte request we return a unique block. - const std::size_t kMinBlockSizeBytes = 16; + // Keeping things rounded and aligned will help us avoid creating tiny and/or + // badly misaligned free blocks. Also ensure even for a 0-byte request we + // return a unique block. const std::size_t kMinAlignment = 16; size = std::max(size, kMinBlockSizeBytes); size = AlignUp(size, kMinBlockSizeBytes); @@ -130,87 +209,65 @@ // Worst case how much memory we need. MemoryBlock allocated_block; - // Start looking through the free list. - // If this is slow, we can store another map sorted by size. - for (FreeBlockSet::iterator it = free_blocks_.begin(); - it != free_blocks_.end(); ++it) { - MemoryBlock block = *it; - const std::size_t extra_bytes_for_alignment = - (alignment - AsInteger(block.address) % alignment) % alignment; - const std::size_t aligned_size = size + extra_bytes_for_alignment; - if (block.size >= aligned_size) { - // The block is big enough. We may waste some space due to alignment. - RemoveFreeBlock(it); - const std::size_t remaining_bytes = block.size - aligned_size; - if (remaining_bytes >= kMinBlockSizeBytes) { - AddFreeBlock(AsPointer(AsInteger(block.address) + aligned_size), - remaining_bytes); - allocated_block.size = aligned_size; - } else { - allocated_block.size = block.size; + bool scan_from_front = size > small_allocation_threshold_; + FreeBlockSet::iterator free_block_iter = free_blocks_.end(); + + if (scan_from_front) { + // Start looking through the free list from the front. + for (FreeBlockSet::iterator it = free_blocks_.begin(); + it != free_blocks_.end(); ++it) { + if (it->CanFullfill(size, alignment)) { + free_block_iter = it; + break; } - user_address = AlignUp(block.address, alignment); - allocated_block.address = block.address; - SB_DCHECK(allocated_block.size <= block.size); - break; + } + } else { + // Start looking through the free list from the back. + for (FreeBlockSet::reverse_iterator it = free_blocks_.rbegin(); + it != free_blocks_.rend(); ++it) { + if (it->CanFullfill(size, alignment)) { + free_block_iter = it.base(); + --free_block_iter; + break; + } } } - if (user_address == 0) { - // No free blocks found. - // Allocate one from the fallback allocator. - size = AlignUp(size, alignment); - void* ptr = fallback_allocator_->AllocateForAlignment(size, alignment); + if (free_block_iter == free_blocks_.end()) { + // No free blocks found, allocate one from the fallback allocator. + std::size_t block_size = size; + void* ptr = + fallback_allocator_->AllocateForAlignment(&block_size, alignment); if (ptr == NULL) { return NULL; } - uint8_t* memory_address = reinterpret_cast<uint8_t*>(ptr); - user_address = AlignUp(memory_address, alignment); - allocated_block.size = size; - allocated_block.address = user_address; - - if (memory_address != user_address) { - std::size_t alignment_padding_size = - AsInteger(user_address) - AsInteger(memory_address); - if (alignment_padding_size >= kMinBlockSizeBytes) { - // Register the memory range skipped for alignment as a free block for - // later use. - AddFreeBlock(memory_address, alignment_padding_size); - capacity_ += alignment_padding_size; - } else { - // The memory range skipped for alignment is too small for a free block. - // Adjust the allocated block to include the alignment padding. - allocated_block.size += alignment_padding_size; - allocated_block.address = AsPointer(AsInteger(allocated_block.address) - - alignment_padding_size); - } - } - - capacity_ += allocated_block.size; + free_block_iter = AddFreeBlock(MemoryBlock(ptr, block_size)); + capacity_ += block_size; fallback_allocations_.push_back(ptr); } + + MemoryBlock block = *free_block_iter; + // The block is big enough. We may waste some space due to alignment. + RemoveFreeBlock(free_block_iter); + + MemoryBlock free_block; + block.Allocate(size, alignment, scan_from_front, &allocated_block, + &free_block); + if (free_block.size() > 0) { + SB_DCHECK(free_block.address()); + AddFreeBlock(free_block); + } + user_address = AlignUp(allocated_block.address(), alignment); + SB_DCHECK(allocated_blocks_.find(user_address) == allocated_blocks_.end()); allocated_blocks_[user_address] = allocated_block; - total_allocated_ += allocated_block.size; + total_allocated_ += allocated_block.size(); return user_address; } void ReuseAllocator::Free(void* memory) { - if (!memory) { - return; - } - - AllocatedBlockMap::iterator it = allocated_blocks_.find(memory); - SB_DCHECK(it != allocated_blocks_.end()); - - // Mark this block as free and remove it from the allocated set. - const MemoryBlock& block = (*it).second; - AddFreeBlock(block.address, block.size); - - SB_DCHECK(block.size <= total_allocated_); - total_allocated_ -= block.size; - - allocated_blocks_.erase(it); + bool result = TryFree(memory); + SB_DCHECK(result); } void ReuseAllocator::PrintAllocations() const { @@ -218,7 +275,7 @@ SizesHistogram sizes_histogram; for (AllocatedBlockMap::const_iterator iter = allocated_blocks_.begin(); iter != allocated_blocks_.end(); ++iter) { - std::size_t block_size = iter->second.size; + std::size_t block_size = iter->second.size(); if (sizes_histogram.find(block_size) == sizes_histogram.end()) { sizes_histogram[block_size] = 0; } @@ -232,4 +289,25 @@ SB_LOG(INFO) << "Total allocations: " << allocated_blocks_.size(); } +bool ReuseAllocator::TryFree(void* memory) { + if (!memory) { + return true; + } + + AllocatedBlockMap::iterator it = allocated_blocks_.find(memory); + if (it == allocated_blocks_.end()) { + return false; + } + + // Mark this block as free and remove it from the allocated set. + const MemoryBlock& block = (*it).second; + AddFreeBlock(block); + + SB_DCHECK(block.size() <= total_allocated_); + total_allocated_ -= block.size(); + + allocated_blocks_.erase(it); + return true; +} + } // namespace nb
diff --git a/src/nb/reuse_allocator.h b/src/nb/reuse_allocator.h index 6f4f16a..71d43d8 100644 --- a/src/nb/reuse_allocator.h +++ b/src/nb/reuse_allocator.h
@@ -30,16 +30,32 @@ // maintaining all allocation meta data is outside of the allocated memory. // It is passed a fallback allocator that it can request additional memory // from as needed. +// The default allocation strategy for the allocator is first-fit, i.e. it will +// scan for free blocks sorted by addresses and allocate from the first free +// block that can fulfill the allocation. However, in some situations the +// majority of the allocations can be small ones with some large allocations. +// This may cause serious fragmentations and the failure of large allocations. +// If |small_allocation_threshold| in the ctor is set to a non-zero value, the +// class will allocate small allocations whose sizes are less than or equal to +// the threshold using last-fit, i.e. it will scan from the back to the front +// for free blocks. This way the allocation for large blocks and small blocks +// are separated thus cause much less fragmentations. class ReuseAllocator : public Allocator { public: explicit ReuseAllocator(Allocator* fallback_allocator); + // When |small_allocation_threshold| is non-zero, this class will allocate + // its full capacity from the |fallback_allocator| in the ctor so it is + // possible for the class to use the last-fit allocation strategy. See the + // class comment above for more details. + ReuseAllocator(Allocator* fallback_allocator, + std::size_t capacity, + std::size_t small_allocation_threshold); virtual ~ReuseAllocator(); // Search free memory blocks for an existing one, and if none are large // enough, allocate a new one from no-free memory and return that. void* Allocate(std::size_t size); void* Allocate(std::size_t size, std::size_t alignment); - void* AllocateForAlignment(std::size_t size, std::size_t alignment); // Marks the memory block as being free and it will then become recyclable void Free(void* memory); @@ -49,30 +65,70 @@ void PrintAllocations() const; - private: - // We will allocate from the given allocator whenever we can't find - // pre-used memory to allocate. - Allocator* fallback_allocator_; + bool TryFree(void* memory); - struct MemoryBlock { - void* address; - std::size_t size; + private: + class MemoryBlock { + public: + MemoryBlock() : address_(0), size_(0) {} + MemoryBlock(void* address, std::size_t size) + : address_(address), size_(size) {} + + void* address() const { return address_; } + std::size_t size() const { return size_; } + + void set_address(void* address) { address_ = address; } + void set_size(std::size_t size) { size_ = size; } + bool operator<(const MemoryBlock& other) const { - return address < other.address; + return address_ < other.address_; } + // If the current block and |other| can be combined into a continuous memory + // block, store the conmbined block in the current block and return true. + // Otherwise return false. + bool Merge(const MemoryBlock& other); + // Return true if the current block can be used to fulfill an allocation + // with the given size and alignment. + bool CanFullfill(std::size_t request_size, std::size_t alignment) const; + // Allocate a block from this block with the given size and alignment. + // Store the allocated block in |allocated|. If the rest space is large + // enough to form a block, it will be stored into |free|. Otherwise the + // whole block is stored into |allocated|. + // Note that the call of this function has to ensure that CanFulfill() is + // already called on this block and returns true. + void Allocate(std::size_t request_size, + std::size_t alignment, + bool allocate_from_front, + MemoryBlock* allocated, + MemoryBlock* free) const; + + private: + void* address_; + std::size_t size_; }; + // Freelist sorted by address. typedef std::set<MemoryBlock> FreeBlockSet; // Map from pointers we returned to the user, back to memory blocks. typedef std::map<void*, MemoryBlock> AllocatedBlockMap; - void AddFreeBlock(void* address, std::size_t size); - void RemoveFreeBlock(FreeBlockSet::iterator); + + FreeBlockSet::iterator AddFreeBlock(MemoryBlock block_to_add); + void RemoveFreeBlock(FreeBlockSet::iterator it); FreeBlockSet free_blocks_; AllocatedBlockMap allocated_blocks_; - // A list of allocations made from the fallback allocator. We keep track - // of this so that we can free them all upon our destruction. + // We will allocate from the given allocator whenever we can't find pre-used + // memory to allocate. + Allocator* fallback_allocator_; + + // Any allocations with size less than or equal to the following threshold + // will be allocated from the back of the pool. See the comment of the class + // for more details. + std::size_t small_allocation_threshold_; + + // A list of allocations made from the fallback allocator. We keep track of + // this so that we can free them all upon our destruction. std::vector<void*> fallback_allocations_; // How much we have allocated from the fallback allocator.
diff --git a/src/nb/reuse_allocator_benchmark.cc b/src/nb/reuse_allocator_benchmark.cc index 4117d5c..ac53c08 100644 --- a/src/nb/reuse_allocator_benchmark.cc +++ b/src/nb/reuse_allocator_benchmark.cc
@@ -177,17 +177,14 @@ class DefaultAllocator : public nb::Allocator { public: - void* Allocate(std::size_t size) { return Allocate(size, 0); } - void* Allocate(std::size_t size, std::size_t alignment) { + void* Allocate(std::size_t size) SB_OVERRIDE { return Allocate(size, 0); } + void* Allocate(std::size_t size, std::size_t alignment) SB_OVERRIDE { return SbMemoryAllocateAligned(alignment, size); } - void* AllocateForAlignment(std::size_t size, std::size_t alignment) { - return SbMemoryAllocateAligned(alignment, size); - } - void Free(void* memory) { SbMemoryFree(memory); } - std::size_t GetCapacity() const { return 0; } - std::size_t GetAllocated() const { return 0; } - void PrintAllocations() const {} + void Free(void* memory) SB_OVERRIDE { SbMemoryDeallocate(memory); } + std::size_t GetCapacity() const SB_OVERRIDE { return 0; } + std::size_t GetAllocated() const SB_OVERRIDE { return 0; } + void PrintAllocations() const SB_OVERRIDE {} }; void MemoryPlaybackTest(const std::string& filename) { @@ -213,7 +210,7 @@ SB_DLOG(INFO) << "Allocator high water mark: " << allocator_high_water_mark; SB_DLOG(INFO) << "Elapsed (ms): " << elapsed_time / kSbTimeMillisecond; - SbMemoryFree(fixed_no_free_memory); + SbMemoryDeallocate(fixed_no_free_memory); // Test again using the system allocator, to compare performance. DefaultAllocator default_allocator;
diff --git a/src/nb/reuse_allocator_test.cc b/src/nb/reuse_allocator_test.cc index 658c81a..4d42361 100644 --- a/src/nb/reuse_allocator_test.cc +++ b/src/nb/reuse_allocator_test.cc
@@ -18,6 +18,7 @@ #include "nb/fixed_no_free_allocator.h" #include "nb/scoped_ptr.h" +#include "starboard/configuration.h" #include "starboard/types.h" #include "testing/gtest/include/gtest/gtest.h" @@ -32,14 +33,21 @@ public: static const int kBufferSize = 1 * 1024 * 1024; - ReuseAllocatorTest() { + ReuseAllocatorTest() { ResetAllocator(0); } + + protected: + void ResetAllocator(size_t small_allocation_threshold) { buffer_.reset(new uint8_t[kBufferSize]); fallback_allocator_.reset( new nb::FixedNoFreeAllocator(buffer_.get(), kBufferSize)); - allocator_.reset(new nb::ReuseAllocator(fallback_allocator_.get())); + if (small_allocation_threshold == 0) { + allocator_.reset(new nb::ReuseAllocator(fallback_allocator_.get())); + } else { + allocator_.reset(new nb::ReuseAllocator( + fallback_allocator_.get(), kBufferSize, small_allocation_threshold)); + } } - protected: nb::scoped_array<uint8_t> buffer_; nb::scoped_ptr<nb::FixedNoFreeAllocator> fallback_allocator_; nb::scoped_ptr<nb::ReuseAllocator> allocator_; @@ -99,3 +107,32 @@ allocator_->Free(test_p); allocator_->Free(blocks[0]); } + +TEST_F(ReuseAllocatorTest, SmallAlloc) { + // Recreate allocator with small allocation threshold to 256. + ResetAllocator(256); + + const std::size_t kBlockSizes[] = {117, 193, 509, 1111}; + const std::size_t kAlignment = 16; + void* blocks[] = {NULL, NULL, NULL, NULL}; + for (int i = 0; i < SB_ARRAY_SIZE(kBlockSizes); ++i) { + blocks[i] = allocator_->Allocate(kBlockSizes[i], kAlignment); + } + // The two small allocs should be in the back in reverse order. + EXPECT_GT(reinterpret_cast<uintptr_t>(blocks[0]), + reinterpret_cast<uintptr_t>(blocks[1])); + // Small allocs should has higher address than other allocs. + EXPECT_GT(reinterpret_cast<uintptr_t>(blocks[1]), + reinterpret_cast<uintptr_t>(blocks[3])); + // Non-small allocs are allocated from the front and the first one has the + // lowest address. + EXPECT_LT(reinterpret_cast<uintptr_t>(blocks[2]), + reinterpret_cast<uintptr_t>(blocks[3])); + for (int i = 0; i < SB_ARRAY_SIZE(kBlockSizes); ++i) { + allocator_->Free(blocks[i]); + } + // Should have one single free block equals to the capacity. + void* test_p = allocator_->Allocate(allocator_->GetCapacity()); + EXPECT_TRUE(test_p != NULL); + allocator_->Free(test_p); +}
diff --git a/src/nb/simple_thread.cc b/src/nb/simple_thread.cc new file mode 100644 index 0000000..7bfea39 --- /dev/null +++ b/src/nb/simple_thread.cc
@@ -0,0 +1,62 @@ +/* + * Copyright 2016 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 "nb/simple_thread.h" + +#include "starboard/mutex.h" +#include "starboard/thread.h" +#include "starboard/types.h" +#include "starboard/log.h" + +namespace nb { + +SimpleThread::SimpleThread(const std::string& name) + : thread_(kSbThreadInvalid), name_(name), + join_called_(false) {} + +SimpleThread::~SimpleThread() { + SB_DCHECK(join_called_.load()) << "Join not called on thread."; +} + +void SimpleThread::Start() { + SbThreadEntryPoint entry_point = ThreadEntryPoint; + + thread_ = SbThreadCreate(0, // default stack_size. + kSbThreadNoPriority, // default priority. + kSbThreadNoAffinity, // default affinity. + true, // joinable. + name_.c_str(), entry_point, this); + + // SbThreadCreate() above produced an invalid thread handle. + SB_DCHECK(thread_ != kSbThreadInvalid); + return; +} + +void* SimpleThread::ThreadEntryPoint(void* context) { + SimpleThread* this_ptr = static_cast<SimpleThread*>(context); + this_ptr->Run(); + return NULL; +} + +void SimpleThread::Join() { + SB_DCHECK(join_called_.load() == false); + if (!SbThreadJoin(thread_, NULL)) { + SB_DCHECK(false) << "Could not join thread."; + } + join_called_.store(true); +} + +} // namespace nb
diff --git a/src/nb/simple_thread.h b/src/nb/simple_thread.h new file mode 100644 index 0000000..ce3bbfe --- /dev/null +++ b/src/nb/simple_thread.h
@@ -0,0 +1,57 @@ +/* + * Copyright 2016 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 NB_THREAD_H_ +#define NB_THREAD_H_ + +#include <string> + +#include "nb/atomic.h" +#include "starboard/thread.h" +#include "starboard/types.h" + +namespace nb { + +class SimpleThread { + public: + explicit SimpleThread(const std::string& name); + virtual ~SimpleThread(); + + // Subclasses should override the Run method. + virtual void Run() = 0; + + // Signals to the thread to break out of it's loop. + virtual void Cancel() {} + + // Called by the main thread, this will cause Run() to be invoked + // on another thread. + void Start(); + // Destroys the threads resources. + void Join(); + + private: + static void* ThreadEntryPoint(void* context); + + const std::string name_; + SbThread thread_; + atomic_bool join_called_; + + SB_DISALLOW_COPY_AND_ASSIGN(SimpleThread); +}; + +} // namespace nb + +#endif // NB_THREAD_H_
diff --git a/src/nb/test_thread.h b/src/nb/test_thread.h new file mode 100644 index 0000000..0ea5768 --- /dev/null +++ b/src/nb/test_thread.h
@@ -0,0 +1,73 @@ +// Copyright 2016 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 NB_TEST_THREAD_H_ +#define NB_TEST_THREAD_H_ + +#include "starboard/configuration.h" +#include "starboard/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace nb { + +// TestThread that is a bare bones class wrapper around Starboard +// thread. Subclasses must override Run(). +class TestThread { + public: + TestThread() : thread_(kSbThreadInvalid) {} + virtual ~TestThread() {} + + // Subclasses should override the Run method. + virtual void Run() = 0; + + // Calls SbThreadCreate() with default parameters. + void Start() { + SbThreadEntryPoint entry_point = ThreadEntryPoint; + + thread_ = SbThreadCreate( + 0, // default stack_size. + kSbThreadNoPriority, // default priority. + kSbThreadNoAffinity, // default affinity. + true, // joinable. + "TestThread", + entry_point, + this); + + if (kSbThreadInvalid == thread_) { + ADD_FAILURE_AT(__FILE__, __LINE__) << "Invalid thread."; + } + return; + } + + void Join() { + if (!SbThreadJoin(thread_, NULL)) { + ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not join thread."; + } + } + + private: + static void* ThreadEntryPoint(void* ptr) { + TestThread* this_ptr = static_cast<TestThread*>(ptr); + this_ptr->Run(); + return NULL; + } + + SbThread thread_; + + SB_DISALLOW_COPY_AND_ASSIGN(TestThread); +}; + +} // namespace nb. + +#endif // NB_TEST_THREAD_H_
diff --git a/src/nb/thread_local_object.h b/src/nb/thread_local_object.h new file mode 100644 index 0000000..a303f5a --- /dev/null +++ b/src/nb/thread_local_object.h
@@ -0,0 +1,216 @@ +/* + * Copyright 2016 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 NB_THREAD_LOCAL_OBJECT_H_ +#define NB_THREAD_LOCAL_OBJECT_H_ + +#include <set> + +#include "starboard/configuration.h" +#include "starboard/log.h" +#include "starboard/mutex.h" +#include "starboard/thread.h" + +namespace nb { + +// Like base::ThreadLocalPointer<T> but destroys objects. This is important +// for using ThreadLocalObjects that aren't tied to a singleton, or for +// access by threads which will call join(). +// +// FEATURE COMPARISON TABLE: +// | Thread Join | Container Destroyed +// ------------------------------------------------------------------ +// ThreadLocalPointer<T> | LEAKS | LEAKS +// ThreadLocalObject<T> | Object Destroyed | Objects Destroyed +// +// EXAMPLE: +// ThreadLocalObject<std::map<std::string, int> > map_tls; +// Map* map = map_tls->GetOrCreate(); +// (*map)["my string"] = 15; +// Thread t = new Thread(&map_tls); +// t->start(); +// // t creates it's own thread local map. +// t->join(); // Make sure that thread joins before map_tls is destroyed! +// +// OBJECT DESTRUCTION: +// There are two ways for an object to be destroyed by the ThreadLocalObject. +// The first way is via a thread join. In this case only the object +// associated with the thread is deleted. +// The second way an object is destroyed is by the ThreadLocalObject +// container to be destroyed, in this case ALL thread local objects are +// destroyed. +// +// PERFORMANCE: +// ThreadLocalObject is fast for the Get() function if the object has +// has already been created, requiring one extra pointer dereference +// over ThreadLocalPointer<T>. +template <typename Type> +class ThreadLocalObject { + public: + ThreadLocalObject() : slot_() { + slot_ = SbThreadCreateLocalKey(DeleteEntry); + SB_DCHECK(kSbThreadLocalKeyInvalid != slot_); + constructing_thread_id_ = SbThreadGetId(); + } + + // Enables destruction by any other thread. Otherwise, the class instance + // will warn when a different thread than the constructing destroys this. + void EnableDestructionByAnyThread() { + constructing_thread_id_ = kSbThreadInvalidId; + } + + // Thread Local Objects are destroyed after this call. + ~ThreadLocalObject() { + CheckCurrentThreadAllowedToDestruct(); + if (SB_DLOG_IS_ON(FATAL)) { + SB_DCHECK(entry_set_.size() < 2) + << "Logic error: Some threads may still be accessing the objects that " + << "are about to be destroyed. Only one object is expected and that " + << "should be for the main thread. The caller should ensure that " + << "other threads that access this object are externally " + << "synchronized."; + } + // No locking is done because the entries should not be accessed by + // different threads while this object is shutting down. If access is + // occuring then the caller has a race condition, external to this class. + typedef typename Set::iterator Iter; + for (Iter it = entry_set_.begin(); it != entry_set_.end(); ++it) { + Entry* entry = *it; + SB_DCHECK(entry->owner_ == this); + delete entry->ptr_; + delete entry; + } + + // Cleanup the thread local key. + SbThreadDestroyLocalKey(slot_); + } + + // Warns if there is a misuse of this object. + void CheckCurrentThreadAllowedToDestruct() const { + if (kSbThreadInvalidId == constructing_thread_id_) { + return; // EnableDestructionByAnyThread() called. + } + const SbThreadId curr_thread_id = SbThreadGetId(); + if (curr_thread_id == constructing_thread_id_) { + return; // Same thread that constructed this. + } + + if (SB_DLOG_IS_ON(FATAL)) { + SB_DCHECK(false) + << "ThreadLocalObject<T> was created in thread " + << constructing_thread_id_ << "\nbut was destroyed by " + << curr_thread_id << ". If this is intentional then call " + << "EnableDestructionByAnyThread() to silence this " + << "warning."; + } + } + + // Either returns the created pointer for the current thread, or otherwise + // constructs the object using the default constructor and returns it. + Type* GetOrCreate() { + Type* object = GetIfExists(); + if (!object) { // create object. + object = new Type(); + Entry* entry = new Entry(this, object); + // Insert into the set of tls entries. + // Performance: Its assumed that creation of objects is much less + // frequent than getting an object. + { + starboard::ScopedLock lock(entry_set_mutex_); + entry_set_.insert(entry); + } + SbThreadSetLocalValue(slot_, entry); + } + return object; + } + + // Returns the pointer if it exists in the current thread, otherwise NULL. + Type* GetIfExists() const { + Entry* entry = GetEntryIfExists(); + if (!entry) { return NULL; } + return entry->ptr_; + } + + // Releases ownership of the pointer FROM THE CURRENT THREAD. + // The caller has responsibility to make sure that the pointer is destroyed. + Type* Release() { + if (Entry* entry = GetEntryIfExists()) { + // The entry will no longer run it's destructor on thread join. + SbThreadSetLocalValue(slot_, NULL); // NULL out pointer for TLS. + Type* object = entry->ptr_; + RemoveEntry(entry); + return object; + } else { + return NULL; + } + } + + private: + struct Entry { + Entry(ThreadLocalObject* own, Type* ptr) : owner_(own), ptr_(ptr) { + } + ~Entry() { + ptr_ = NULL; + owner_ = NULL; + } + ThreadLocalObject* owner_; + Type* ptr_; + }; + + // Deletes the TLSEntry. + static void DeleteEntry(void* ptr) { + if (!ptr) { + SB_NOTREACHED(); + return; + } + Entry* entry = reinterpret_cast<Entry*>(ptr); + ThreadLocalObject* tls = entry->owner_; + Type* object = entry->ptr_; + tls->RemoveEntry(entry); + delete object; + } + + void RemoveEntry(Entry* entry) { + { + starboard::ScopedLock lock(entry_set_mutex_); + entry_set_.erase(entry); + } + delete entry; + } + + Entry* GetEntryIfExists() const { + void* ptr = SbThreadGetLocalValue(slot_); + Entry* entry = static_cast<Entry*>(ptr); + return entry; + } + + typedef std::set<Entry*> Set; + // Allows GetIfExists() to be const. + mutable SbThreadLocalKey slot_; + // entry_set_ contains all the outstanding entries for the thread local + // objects that have been created. + Set entry_set_; + mutable starboard::Mutex entry_set_mutex_; + // Used to warn when there is a mismatch between thread that constructed and + // thread that destroyed this object. + SbThreadId constructing_thread_id_; + + SB_DISALLOW_COPY_AND_ASSIGN(ThreadLocalObject<Type>); +}; + +} // namespace nb + +#endif // NB_THREAD_LOCAL_OBJECT_H_
diff --git a/src/nb/thread_local_object_test.cc b/src/nb/thread_local_object_test.cc new file mode 100644 index 0000000..9fce6d6 --- /dev/null +++ b/src/nb/thread_local_object_test.cc
@@ -0,0 +1,204 @@ +// Copyright 2016 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 <map> +#include <string> + +#include "nb/test_thread.h" +#include "nb/atomic.h" +#include "nb/thread_local_object.h" +#include "nb/scoped_ptr.h" +#include "starboard/mutex.h" +#include "starboard/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace nb { +namespace { + +// Simple class that counts the number of instances alive. +struct CountsInstances { + CountsInstances() { s_instances_.increment(); } + ~CountsInstances() { s_instances_.decrement(); } + static atomic_int32_t s_instances_; + static int NumInstances() { return s_instances_.load(); } + static void ResetNumInstances() { s_instances_.exchange(0); } +}; +nb::atomic_int32_t CountsInstances::s_instances_(0); + +// A simple thread that just creates the an object from the supplied +// ThreadLocalObject<T> and then exits. +template <typename TYPE> +class CreateThreadLocalObjectThenExit : public TestThread { + public: + explicit CreateThreadLocalObjectThenExit( + ThreadLocalObject<TYPE>* tlo) : tlo_(tlo) {} + + virtual void Run() { + // volatile as a defensive measure to prevent compiler from optimizing this + // statement out. + volatile TYPE* val = tlo_->GetOrCreate(); + } + ThreadLocalObject<TYPE>* tlo_; +}; + +// A simple thread that just deletes the object supplied on a thread and then +// exists. +template <typename TYPE> +class DestroyTypeOnThread : public TestThread { + public: + explicit DestroyTypeOnThread(TYPE* ptr) + : ptr_(ptr) {} + virtual void Run() { + ptr_.reset(NULL); // Destroys the object. + } + private: + nb::scoped_ptr<TYPE> ptr_; +}; + +// Tests the expectation that a ThreadLocalObject can be simply used by +// the main thread. +TEST(ThreadLocalObject, MainThread) { + ThreadLocalObject<bool> tlo_bool; + EXPECT_TRUE(NULL == tlo_bool.GetIfExists()); + bool* the_bool = tlo_bool.GetOrCreate(); + EXPECT_TRUE(the_bool != NULL); + EXPECT_FALSE(*the_bool); + *the_bool = true; + EXPECT_TRUE(*(tlo_bool.GetIfExists())); +} + +// Tests the expectation that a ThreadLocalObject can be used on +// complex objects type (i.e. non pod types). +TEST(ThreadLocalObject, MainThreadComplexObject) { + typedef std::map<std::string, int> Map; + ThreadLocalObject<Map> map_tlo; + EXPECT_FALSE(map_tlo.GetIfExists()); + ASSERT_TRUE(map_tlo.GetOrCreate()); + Map* map = map_tlo.GetIfExists(); + const Map* const_map = map_tlo.GetIfExists(); + ASSERT_TRUE(map); + ASSERT_TRUE(const_map); + // If the object is properly constructed then this find operation + // should succeed. + (*map)["my string"] = 15; + ASSERT_EQ(15, (*map)["my string"]); +} + +// Tests that when a ThreadLocalObject is destroyed on the main thread that +// the pointers it contained are also destroyed. +TEST(ThreadLocalObject, DestroysObjectOnTLODestruction) { + CountsInstances::ResetNumInstances(); + typedef ThreadLocalObject<CountsInstances> TLO; + + // Create the TLO object and then immediately destroy it. + nb::scoped_ptr<TLO> tlo_ptr(new TLO); + tlo_ptr->GetOrCreate(); // Instantiate the internal object. + EXPECT_EQ(1, CountsInstances::NumInstances()); + tlo_ptr.reset(NULL); // Should destroy all outstanding allocs. + // Now the TLO is destroyed and therefore the destructor should run on the + // internal object. + EXPECT_EQ(0, CountsInstances::NumInstances()); + + CountsInstances::ResetNumInstances(); +} + +// Tests the expectation that the object can be released and that the pointer +// won't be deleted when the ThreadLocalObject that created it is destroyed. +TEST(ThreadLocalObject, ReleasesObject) { + CountsInstances::ResetNumInstances(); + typedef ThreadLocalObject<CountsInstances> TLO; + + nb::scoped_ptr<TLO> tlo_ptr(new TLO); + // Instantiate the internal object. + tlo_ptr->GetOrCreate(); + // Now release the pointer into the container. + nb::scoped_ptr<CountsInstances> last_ref(tlo_ptr->Release()); + // Destroying the TLO should not trigger the destruction of the object, + // because it was released. + tlo_ptr.reset(NULL); + // 1 instance left, which is held in last_ref. + EXPECT_EQ(1, CountsInstances::NumInstances()); + last_ref.reset(NULL); // Now the object should be destroyed and the + // instance count drops to 0. + EXPECT_EQ(0, CountsInstances::NumInstances()); + CountsInstances::ResetNumInstances(); +} + +// Tests the expectation that a thread that creates an object from +// the ThreadLocalObject store will automatically be destroyed by the +// thread joining. +TEST(ThreadLocalObject, ThreadJoinDestroysObject) { + CountsInstances::ResetNumInstances(); + typedef ThreadLocalObject<CountsInstances> TLO; + + nb::scoped_ptr<TLO> tlo(new TLO); + { + TestThread* thread = + new CreateThreadLocalObjectThenExit<CountsInstances>(tlo.get()); + thread->Start(); + thread->Join(); + // Once the thread joins, the object should be deleted and the instance + // counter falls to 0. + EXPECT_EQ(0, CountsInstances::NumInstances()); + delete thread; + } + + tlo.reset(NULL); // Now TLO destructor runs. + EXPECT_EQ(0, CountsInstances::NumInstances()); + CountsInstances::ResetNumInstances(); +} + +// Tests the expectation that objects created on the main thread are not +// leaked. +TEST(ThreadLocalObject, NoLeaksOnMainThread) { + CountsInstances::ResetNumInstances(); + + ThreadLocalObject<CountsInstances>* tlo = + new ThreadLocalObject<CountsInstances>; + tlo->EnableDestructionByAnyThread(); + + // Creates the object on the main thread. This is important because the + // main thread will never join and therefore at-exit functions won't get + // run. + CountsInstances* main_thread_object = tlo->GetOrCreate(); + + EXPECT_EQ(1, CountsInstances::NumInstances()); + + // Thread will simply create the thread local object (CountsInstances) + // and then return. + nb::scoped_ptr<TestThread> thread_ptr( + new CreateThreadLocalObjectThenExit<CountsInstances>(tlo)); + thread_ptr->Start(); // Object is now created. + thread_ptr->Join(); // ...then destroyed. + thread_ptr.reset(NULL); + + // Only main_thread_object should be alive now, therefore the count is 1. + EXPECT_EQ(1, CountsInstances::NumInstances()); + + // We COULD destroy the TLO on the main thread, but to be even fancier lets + // create a thread that will destroy the object on a back ground thread. + // The end result is that the TLO entry should be cleared out. + thread_ptr.reset( + new DestroyTypeOnThread<ThreadLocalObject<CountsInstances> >(tlo)); + thread_ptr->Start(); + thread_ptr->Join(); + thread_ptr.reset(NULL); + + // Now we expect that number of instances to be 0. + EXPECT_EQ(0, CountsInstances::NumInstances()); + CountsInstances::ResetNumInstances(); +} + +} // anonymous namespace +} // nb namespace
diff --git a/src/net/base/data_url.cc b/src/net/base/data_url.cc index b7309cb..53a5ea8 100644 --- a/src/net/base/data_url.cc +++ b/src/net/base/data_url.cc
@@ -13,6 +13,7 @@ #include "base/string_split.h" #include "base/string_util.h" #include "googleurl/src/gurl.h" +#include "nb/memory_scope.h" #include "net/base/escape.h" namespace net { @@ -20,6 +21,7 @@ // static bool DataURL::Parse(const GURL& url, std::string* mime_type, std::string* charset, std::string* data) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(mime_type->empty()); DCHECK(charset->empty()); std::string::const_iterator begin = url.spec().begin();
diff --git a/src/net/base/file_stream_unittest.cc b/src/net/base/file_stream_unittest.cc index 04b613d..2cdb252 100644 --- a/src/net/base/file_stream_unittest.cc +++ b/src/net/base/file_stream_unittest.cc
@@ -437,6 +437,8 @@ drainable->DidConsume(rv); total_bytes_written += rv; } + rv = stream.FlushSync(); + EXPECT_EQ(OK, rv); ok = file_util::GetFileSize(temp_file_path(), &file_size); EXPECT_TRUE(ok); EXPECT_EQ(file_size, total_bytes_written); @@ -533,6 +535,8 @@ drainable->DidConsume(rv); total_bytes_written += rv; } + rv = stream.FlushSync(); + EXPECT_EQ(OK, rv); ok = file_util::GetFileSize(temp_file_path(), &file_size); EXPECT_TRUE(ok); EXPECT_EQ(file_size, kTestDataSize * 2); @@ -881,6 +885,8 @@ EXPECT_LT(0, rv); EXPECT_EQ(kTestDataSize, total_bytes_written); + rv = stream->FlushSync(); + EXPECT_EQ(OK, rv); stream.reset(); ok = file_util::GetFileSize(temp_file_path(), &file_size);
diff --git a/src/net/base/host_resolver_impl.cc b/src/net/base/host_resolver_impl.cc index 11ccd9f..9c1ac57 100644 --- a/src/net/base/host_resolver_impl.cc +++ b/src/net/base/host_resolver_impl.cc
@@ -44,6 +44,7 @@ #include "net/dns/dns_protocol.h" #include "net/dns/dns_response.h" #include "net/dns/dns_transaction.h" +#include "nb/memory_scope.h" #if defined(OS_WIN) #include "net/base/winsock_init.h" @@ -580,6 +581,7 @@ ~ProcTask() {} void StartLookupAttempt() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(origin_loop_->BelongsToCurrentThread()); base::TimeTicks start_time = base::TimeTicks::Now(); ++attempt_number_;
diff --git a/src/net/base/host_resolver_proc.cc b/src/net/base/host_resolver_proc.cc index 8950f5b..a4512e9 100644 --- a/src/net/base/host_resolver_proc.cc +++ b/src/net/base/host_resolver_proc.cc
@@ -12,6 +12,7 @@ #include "net/base/dns_reloader.h" #include "net/base/net_errors.h" #include "net/base/sys_addrinfo.h" +#include "nb/memory_scope.h" #if defined(OS_STARBOARD) #include "starboard/socket.h" @@ -133,6 +134,7 @@ HostResolverFlags host_resolver_flags, AddressList* addrlist, int* os_error) { + TRACK_MEMORY_SCOPE("Network"); if (os_error) *os_error = 0;
diff --git a/src/net/base/net_errors.h b/src/net/base/net_errors.h index 95a81e9..43cffab 100644 --- a/src/net/base/net_errors.h +++ b/src/net/base/net_errors.h
@@ -49,7 +49,7 @@ NET_EXPORT Error MapSocketError(SbSocketError error); // Gets the last socket error as a net error. -SB_C_INLINE Error MapLastSocketError(SbSocket socket) { +static SB_C_INLINE Error MapLastSocketError(SbSocket socket) { return MapSocketError(SbSocketGetLastError(socket)); } @@ -57,7 +57,7 @@ NET_EXPORT Error MapSystemError(SbSystemError error); // Gets the last system error as a net error. -SB_C_INLINE Error MapLastSystemError() { +static SB_C_INLINE Error MapLastSystemError() { return MapSystemError(SbSystemGetLastError()); }
diff --git a/src/net/base/net_util.cc b/src/net/base/net_util.cc index a5c6c64..7b406ba 100644 --- a/src/net/base/net_util.cc +++ b/src/net/base/net_util.cc
@@ -63,6 +63,7 @@ #include "net/base/escape.h" #include "net/base/mime_util.h" #include "net/base/net_module.h" + #if defined(OS_WIN) #include "net/base/winsock_init.h" #endif @@ -1870,7 +1871,16 @@ // TODO(jar): The following is a simple estimate of IPv6 support. We may need // to do a test resolution, and a test connection, to REALLY verify support. IPv6SupportResult TestIPv6SupportInternal() { -#if defined(OS_ANDROID) || defined(__LB_ANDROID__) +#if defined(OS_STARBOARD) + SbSocket test_socket = + SbSocketCreate(kSbSocketAddressTypeIpv6, kSbSocketProtocolTcp); + if (!SbSocketIsValid(test_socket)) { + return IPv6SupportResult(false, IPV6_CANNOT_CREATE_SOCKETS, + SbSystemGetLastError()); + } + SbSocketDestroy(test_socket); + return IPv6SupportResult(true, IPV6_SUPPORT_MAX, 0); +#elif defined(OS_ANDROID) || defined(__LB_ANDROID__) // TODO: We should fully implement IPv6 probe once 'getifaddrs' API available; // Another approach is implementing the similar feature by // java.net.NetworkInterface through JNI.
diff --git a/src/net/base/net_util_unittest.cc b/src/net/base/net_util_unittest.cc index 8ec69ad..d6102b2 100644 --- a/src/net/base/net_util_unittest.cc +++ b/src/net/base/net_util_unittest.cc
@@ -600,7 +600,7 @@ "%E9%A1%B5.doc"}, {L"D:\\plane1\\\xD835\xDC00\xD835\xDC01.txt", // Math alphabet "AB" "file:///D:/plane1/%F0%9D%90%80%F0%9D%90%81.txt"}, -#elif defined(OS_POSIX) +#elif defined(OS_POSIX) || defined(OS_STARBOARD) {L"/foo/bar.txt", "file:///foo/bar.txt"}, {L"/foo/BAR.txt", "file:///foo/BAR.txt"}, {L"/C:/foo/bar.txt", "file:///C:/foo/bar.txt"}, @@ -647,7 +647,7 @@ {L"\\\\foo\\bar.txt", "file:/foo/bar.txt"}, {L"\\\\foo\\bar.txt", "file://foo\\bar.txt"}, {L"C:\\foo\\bar.txt", "file:\\\\\\c:/foo/bar.txt"}, -#elif defined(OS_POSIX) +#elif defined(OS_POSIX) || defined(OS_STARBOARD) {L"/c:/foo/bar.txt", "file:/c:/foo/bar.txt"}, {L"/c:/foo/bar.txt", "file:///c:/foo/bar.txt"}, {L"/foo/bar.txt", "file:/foo/bar.txt"},
diff --git a/src/net/http/http_auth_handler_ntlm.cc b/src/net/http/http_auth_handler_ntlm.cc index ceca94a..aa0142a 100644 --- a/src/net/http/http_auth_handler_ntlm.cc +++ b/src/net/http/http_auth_handler_ntlm.cc
@@ -15,7 +15,7 @@ #if defined(OS_STARBOARD) #include "starboard/memory.h" -#define free SbMemoryFree +#define free SbMemoryDeallocate #endif namespace net {
diff --git a/src/net/http/http_auth_handler_ntlm_portable.cc b/src/net/http/http_auth_handler_ntlm_portable.cc index dbcfd94..63cdcee 100644 --- a/src/net/http/http_auth_handler_ntlm_portable.cc +++ b/src/net/http/http_auth_handler_ntlm_portable.cc
@@ -7,7 +7,7 @@ #if defined(OS_STARBOARD) #include "starboard/memory.h" #define malloc SbMemoryAllocate -#define free SbMemoryFree +#define free SbMemoryDeallocate #else #include <stdlib.h> #endif
diff --git a/src/net/http/http_pipelined_host_pool.h b/src/net/http/http_pipelined_host_pool.h index 7a0a678..1f86618 100644 --- a/src/net/http/http_pipelined_host_pool.h +++ b/src/net/http/http_pipelined_host_pool.h
@@ -81,7 +81,7 @@ base::Value* PipelineInfoToValue() const; private: -#if defined(__LB_SHELL__) +#if defined(__LB_SHELL__) || defined(OS_STARBOARD) typedef std::map<HttpPipelinedHost::Key, HttpPipelinedHost*> HostMap; #else typedef std::map<const HttpPipelinedHost::Key, HttpPipelinedHost*> HostMap;
diff --git a/src/net/http/http_stream_factory_impl_job.cc b/src/net/http/http_stream_factory_impl_job.cc index 77422ce..9554ce6 100644 --- a/src/net/http/http_stream_factory_impl_job.cc +++ b/src/net/http/http_stream_factory_impl_job.cc
@@ -12,6 +12,7 @@ #include "base/stringprintf.h" #include "base/values.h" #include "build/build_config.h" +#include "nb/memory_scope.h" #include "net/base/connection_type_histograms.h" #include "net/base/net_log.h" #include "net/base/net_util.h" @@ -267,6 +268,7 @@ } void HttpStreamFactoryImpl::Job::OnStreamReadyCallback() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(stream_.get()); DCHECK(!IsPreconnecting()); if (IsOrphaned()) {
diff --git a/src/net/net.gyp b/src/net/net.gyp index 3d7b9d4..d13a159 100644 --- a/src/net/net.gyp +++ b/src/net/net.gyp
@@ -57,6 +57,7 @@ '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', '../googleurl/googleurl.gyp:googleurl', '../crypto/crypto.gyp:crypto', + '../nb/nb.gyp:nb', '../third_party/icu/icu.gyp:icui18n', '../third_party/icu/icu.gyp:icuuc', '../third_party/zlib/zlib.gyp:zlib',
diff --git a/src/net/quic/crypto/crypto_framer_test.cc b/src/net/quic/crypto/crypto_framer_test.cc index eed175f..b959541 100644 --- a/src/net/quic/crypto/crypto_framer_test.cc +++ b/src/net/quic/crypto/crypto_framer_test.cc
@@ -10,6 +10,7 @@ #include "net/quic/crypto/crypto_framer.h" #include "net/quic/crypto/crypto_protocol.h" #include "net/quic/test_tools/quic_test_utils.h" +#include "net/test/disabled_if_big_endian.h" using base::StringPiece; using std::map; @@ -62,7 +63,7 @@ } // namespace test -TEST(CryptoFramerTest, ConstructHandshakeMessage) { +TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructHandshakeMessage)) { CryptoHandshakeMessage message; message.tag = 0xFFAA7733; message.tag_value_map[0x12345678] = "abcdef"; @@ -107,7 +108,8 @@ AsChars(packet), arraysize(packet)); } -TEST(CryptoFramerTest, ConstructHandshakeMessageWithTwoKeys) { +TEST(CryptoFramerTest, + DISABLED_IF_BIG_ENDIAN(ConstructHandshakeMessageWithTwoKeys)) { CryptoHandshakeMessage message; message.tag = 0xFFAA7733; message.tag_value_map[0x12345678] = "abcdef"; @@ -166,7 +168,7 @@ EXPECT_TRUE(data.get() == NULL); } -TEST(CryptoFramerTest, ProcessInput) { +TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ProcessInput)) { test::TestCryptoVisitor visitor; CryptoFramer framer; framer.set_visitor(&visitor); @@ -201,7 +203,7 @@ EXPECT_EQ("ghijk", visitor.message_maps_[0][0x12345679]); } -TEST(CryptoFramerTest, ProcessInputIncrementally) { +TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ProcessInputIncrementally)) { test::TestCryptoVisitor visitor; CryptoFramer framer; framer.set_visitor(&visitor); @@ -237,7 +239,7 @@ EXPECT_EQ("ghijk", visitor.message_maps_[0][0x12345679]); } -TEST(CryptoFramerTest, ProcessInputTagsOutOfOrder) { +TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ProcessInputTagsOutOfOrder)) { test::TestCryptoVisitor visitor; CryptoFramer framer; framer.set_visitor(&visitor); @@ -275,7 +277,7 @@ EXPECT_EQ(QUIC_CRYPTO_TOO_MANY_ENTRIES, framer.error()); } -TEST(CryptoFramerTest, ProcessInputInvalidLength) { +TEST(CryptoFramerTest, DISABLED_IF_BIG_ENDIAN(ProcessInputInvalidLength)) { test::TestCryptoVisitor visitor; CryptoFramer framer; framer.set_visitor(&visitor);
diff --git a/src/net/quic/crypto/null_decrypter_test.cc b/src/net/quic/crypto/null_decrypter_test.cc index ad73cea..f76de31 100644 --- a/src/net/quic/crypto/null_decrypter_test.cc +++ b/src/net/quic/crypto/null_decrypter_test.cc
@@ -4,13 +4,14 @@ #include "net/quic/crypto/null_decrypter.h" #include "net/quic/test_tools/quic_test_utils.h" +#include "net/test/disabled_if_big_endian.h" using base::StringPiece; namespace net { namespace test { -TEST(NullDecrypterTest, Decrypt) { +TEST(NullDecrypterTest, DISABLED_IF_BIG_ENDIAN(Decrypt)) { unsigned char expected[] = { // fnv hash 0x47, 0x11, 0xea, 0x5f,
diff --git a/src/net/quic/crypto/null_encrypter_test.cc b/src/net/quic/crypto/null_encrypter_test.cc index 9f2cec2..9f9f2c4 100644 --- a/src/net/quic/crypto/null_encrypter_test.cc +++ b/src/net/quic/crypto/null_encrypter_test.cc
@@ -4,13 +4,14 @@ #include "net/quic/crypto/null_encrypter.h" #include "net/quic/test_tools/quic_test_utils.h" +#include "net/test/disabled_if_big_endian.h" using base::StringPiece; namespace net { namespace test { -TEST(NullEncrypterTest, Encrypt) { +TEST(NullEncrypterTest, DISABLED_IF_BIG_ENDIAN(Encrypt)) { unsigned char expected[] = { // fnv hash 0x47, 0x11, 0xea, 0x5f,
diff --git a/src/net/quic/quic_connection_test.cc b/src/net/quic/quic_connection_test.cc index 6e14df7..97c9399 100644 --- a/src/net/quic/quic_connection_test.cc +++ b/src/net/quic/quic_connection_test.cc
@@ -13,6 +13,7 @@ #include "net/quic/test_tools/mock_clock.h" #include "net/quic/test_tools/quic_test_utils.h" #include "net/quic/quic_utils.h" +#include "net/test/disabled_if_big_endian.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -794,7 +795,8 @@ EXPECT_TRUE(last_feedback() == NULL); } -TEST_F(QuicConnectionTest, WithQuicCongestionFeedbackFrame) { +TEST_F(QuicConnectionTest, + DISABLED_IF_BIG_ENDIAN(WithQuicCongestionFeedbackFrame)) { QuicCongestionFeedbackFrame info; info.type = kFixRate; info.fix_rate.bitrate_in_bytes_per_second = 123;
diff --git a/src/net/quic/quic_framer_test.cc b/src/net/quic/quic_framer_test.cc index 25c3f39..9ef87dc 100644 --- a/src/net/quic/quic_framer_test.cc +++ b/src/net/quic/quic_framer_test.cc
@@ -16,6 +16,7 @@ #include "net/quic/quic_protocol.h" #include "net/quic/quic_utils.h" #include "net/quic/test_tools/quic_test_utils.h" +#include "net/test/disabled_if_big_endian.h" using base::hash_set; using base::StringPiece; @@ -249,7 +250,7 @@ EXPECT_EQ(QUIC_INVALID_PACKET_HEADER, framer_.error()); } -TEST_F(QuicFramerTest, LargePacket) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(LargePacket)) { unsigned char packet[kMaxPacketSize + 1] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -271,13 +272,14 @@ EXPECT_FALSE(framer_.ProcessPacket(self_address_, peer_address_, encrypted)); ASSERT_TRUE(visitor_.header_.get()); + // Make sure we've parsed the packet header, so we can send an error. EXPECT_EQ(GG_UINT64_C(0xFEDCBA9876543210), visitor_.header_->guid); // Make sure the correct error is propogated. EXPECT_EQ(QUIC_PACKET_TOO_LARGE, framer_.error()); } -TEST_F(QuicFramerTest, PacketHeader) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(PacketHeader)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -318,7 +320,7 @@ } } -TEST_F(QuicFramerTest, StreamFrame) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(StreamFrame)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -435,7 +437,7 @@ EXPECT_EQ(0u, visitor_.ack_frames_.size()); } -TEST_F(QuicFramerTest, RevivedStreamFrame) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(RevivedStreamFrame)) { unsigned char payload[] = { // frame count 0x01, @@ -486,7 +488,7 @@ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data); } -TEST_F(QuicFramerTest, StreamFrameInFecGroup) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(StreamFrameInFecGroup)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -539,7 +541,7 @@ EXPECT_EQ("hello world!", visitor_.stream_frames_[0]->data); } -TEST_F(QuicFramerTest, AckFrame) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(AckFrame)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -606,7 +608,7 @@ } } -TEST_F(QuicFramerTest, CongestionFeedbackFrameTCP) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(CongestionFeedbackFrameTCP)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -666,7 +668,8 @@ } } -TEST_F(QuicFramerTest, CongestionFeedbackFrameInterArrival) { +TEST_F(QuicFramerTest, + DISABLED_IF_BIG_ENDIAN(CongestionFeedbackFrameInterArrival)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -775,7 +778,7 @@ } } -TEST_F(QuicFramerTest, CongestionFeedbackFrameFixRate) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(CongestionFeedbackFrameFixRate)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -858,7 +861,7 @@ EXPECT_EQ(QUIC_INVALID_FRAME_DATA, framer_.error()); } -TEST_F(QuicFramerTest, RstStreamFrame) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(RstStreamFrame)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -923,7 +926,7 @@ } } -TEST_F(QuicFramerTest, ConnectionCloseFrame) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConnectionCloseFrame)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -1008,7 +1011,7 @@ } } -TEST_F(QuicFramerTest, FecPacket) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(FecPacket)) { unsigned char packet[] = { // guid 0x10, 0x32, 0x54, 0x76, @@ -1047,7 +1050,7 @@ EXPECT_EQ("abcdefghijklmnop", fec_data.redundancy); } -TEST_F(QuicFramerTest, ConstructStreamFramePacket) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructStreamFramePacket)) { QuicPacketHeader header; header.guid = GG_UINT64_C(0xFEDCBA9876543210); header.packet_sequence_number = GG_UINT64_C(0x123456789ABC); @@ -1102,7 +1105,7 @@ AsChars(packet), arraysize(packet)); } -TEST_F(QuicFramerTest, ConstructAckFramePacket) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructAckFramePacket)) { QuicPacketHeader header; header.guid = GG_UINT64_C(0xFEDCBA9876543210); header.packet_sequence_number = GG_UINT64_C(0x123456789ABC); @@ -1154,7 +1157,8 @@ AsChars(packet), arraysize(packet)); } -TEST_F(QuicFramerTest, ConstructCongestionFeedbackFramePacketTCP) { +TEST_F(QuicFramerTest, + DISABLED_IF_BIG_ENDIAN(ConstructCongestionFeedbackFramePacketTCP)) { QuicPacketHeader header; header.guid = GG_UINT64_C(0xFEDCBA9876543210); header.packet_sequence_number = GG_UINT64_C(0x123456789ABC); @@ -1201,7 +1205,9 @@ AsChars(packet), arraysize(packet)); } -TEST_F(QuicFramerTest, ConstructCongestionFeedbackFramePacketInterArrival) { +TEST_F(QuicFramerTest, + DISABLED_IF_BIG_ENDIAN( + ConstructCongestionFeedbackFramePacketInterArrival)) { QuicPacketHeader header; header.guid = GG_UINT64_C(0xFEDCBA9876543210); header.packet_sequence_number = GG_UINT64_C(0x123456789ABC); @@ -1274,7 +1280,8 @@ AsChars(packet), arraysize(packet)); } -TEST_F(QuicFramerTest, ConstructCongestionFeedbackFramePacketFixRate) { +TEST_F(QuicFramerTest, + DISABLED_IF_BIG_ENDIAN(ConstructCongestionFeedbackFramePacketFixRate)) { QuicPacketHeader header; header.guid = GG_UINT64_C(0xFEDCBA9876543210); header.packet_sequence_number = GG_UINT64_C(0x123456789ABC); @@ -1337,7 +1344,7 @@ ASSERT_TRUE(data == NULL); } -TEST_F(QuicFramerTest, ConstructRstFramePacket) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructRstFramePacket)) { QuicPacketHeader header; header.guid = GG_UINT64_C(0xFEDCBA9876543210); header.packet_sequence_number = GG_UINT64_C(0x123456789ABC); @@ -1393,7 +1400,7 @@ AsChars(packet), arraysize(packet)); } -TEST_F(QuicFramerTest, ConstructCloseFramePacket) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructCloseFramePacket)) { QuicPacketHeader header; header.guid = GG_UINT64_C(0xFEDCBA9876543210); header.packet_sequence_number = GG_UINT64_C(0x123456789ABC); @@ -1460,7 +1467,7 @@ AsChars(packet), arraysize(packet)); } -TEST_F(QuicFramerTest, ConstructFecPacket) { +TEST_F(QuicFramerTest, DISABLED_IF_BIG_ENDIAN(ConstructFecPacket)) { QuicPacketHeader header; header.guid = GG_UINT64_C(0xFEDCBA9876543210); header.packet_sequence_number = (GG_UINT64_C(0x123456789ABC));
diff --git a/src/net/socket/client_socket_handle.cc b/src/net/socket/client_socket_handle.cc index 20ab672..cffb902 100644 --- a/src/net/socket/client_socket_handle.cc +++ b/src/net/socket/client_socket_handle.cc
@@ -9,6 +9,7 @@ #include "base/compiler_specific.h" #include "base/metrics/histogram.h" #include "base/logging.h" +#include "nb/memory_scope.h" #include "net/base/net_errors.h" #include "net/socket/client_socket_pool.h" #include "net/socket/client_socket_pool_histograms.h" @@ -104,6 +105,7 @@ } void ClientSocketHandle::OnIOComplete(int result) { + TRACK_MEMORY_SCOPE("Network"); CompletionCallback callback = user_callback_; user_callback_.Reset(); HandleInitCompletion(result); @@ -111,6 +113,7 @@ } void ClientSocketHandle::HandleInitCompletion(int result) { + TRACK_MEMORY_SCOPE("Network"); CHECK_NE(ERR_IO_PENDING, result); if (result != OK) { if (!socket_.get())
diff --git a/src/net/socket/ssl_client_socket_openssl.cc b/src/net/socket/ssl_client_socket_openssl.cc index 1adcfb5..1505cb0 100644 --- a/src/net/socket/ssl_client_socket_openssl.cc +++ b/src/net/socket/ssl_client_socket_openssl.cc
@@ -1092,7 +1092,7 @@ DCHECK(recv_buffer_); int ret = BIO_write(transport_bio_, recv_buffer_->data(), result); // A write into a memory BIO should always succeed. - CHECK_EQ(result, ret); + DCHECK_EQ(result, ret); } recv_buffer_ = NULL; transport_recv_busy_ = false;
diff --git a/src/net/socket/tcp_client_socket_starboard.cc b/src/net/socket/tcp_client_socket_starboard.cc index 516be9d..c970ac7 100644 --- a/src/net/socket/tcp_client_socket_starboard.cc +++ b/src/net/socket/tcp_client_socket_starboard.cc
@@ -23,6 +23,7 @@ #include "base/posix/eintr_wrapper.h" #include "base/string_util.h" #include "base/stringprintf.h" +#include "nb/memory_scope.h" #include "net/base/address_family.h" #include "net/base/connection_type_histograms.h" #include "net/base/io_buffer.h" @@ -134,7 +135,27 @@ net_log_.EndEvent(NetLog::TYPE_SOCKET_ALIVE); } +int TCPClientSocketStarboard::AdoptSocket(SbSocket socket) { + DCHECK(!SbSocketIsValid(socket_)); + + int error = SetupSocket(socket); + if (error) { + return error; + } + + socket_ = socket; + + // This is to make GetPeerAddress() work. It's up to the caller to ensure that + // |address_| contains a reasonable address for this socket. (i.e. at least + // match IPv4 vs IPv6!). + current_address_index_ = 0; + use_history_.set_was_ever_connected(); + + return OK; +} + int TCPClientSocketStarboard::Connect(const CompletionCallback& callback) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(CalledOnValidThread()); // If this socket is already valid, then just return OK. @@ -167,6 +188,7 @@ // pretty much cribbed directly from TCPClientSocketLibevent and/or // TCPClientSocketWin, take your pick, they're identical int TCPClientSocketStarboard::DoConnectLoop(int result) { + TRACK_MEMORY_SCOPE("Network"); DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE); int rv = result; @@ -192,6 +214,7 @@ } int TCPClientSocketStarboard::DoConnect() { + TRACK_MEMORY_SCOPE("Network"); DCHECK_GE(current_address_index_, 0); DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size())); @@ -238,6 +261,7 @@ } int TCPClientSocketStarboard::DoConnectComplete(int result) { + TRACK_MEMORY_SCOPE("Network"); // Log the end of this attempt (and any OS error it threw). int error = connect_error_; connect_error_ = OK; @@ -268,6 +292,7 @@ } void TCPClientSocketStarboard::DidCompleteConnect() { + TRACK_MEMORY_SCOPE("Network"); DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE); connect_error_ = SbSocketIsConnected(socket_) ? OK : ERR_FAILED; @@ -279,6 +304,7 @@ } void TCPClientSocketStarboard::LogConnectCompletion(int net_error) { + TRACK_MEMORY_SCOPE("Network"); if (net_error == OK) UpdateConnectionTypeHistograms(CONNECTION_ANY); @@ -403,6 +429,7 @@ int TCPClientSocketStarboard::Read(IOBuffer* buf, int buf_len, const CompletionCallback& callback) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(SbSocketIsValid(socket_)); DCHECK(!waiting_connect()); DCHECK(!waiting_read_); @@ -440,6 +467,7 @@ } void TCPClientSocketStarboard::DidCompleteRead() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(waiting_read_); int bytes_read = SbSocketReceiveFrom(socket_, read_buf_->data(), read_buf_len_, NULL); @@ -466,6 +494,7 @@ } void TCPClientSocketStarboard::DoReadCallback(int rv) { + TRACK_MEMORY_SCOPE("Network"); DCHECK_NE(rv, ERR_IO_PENDING); // since Run may result in Read being called, clear read_callback_ up front. @@ -477,6 +506,7 @@ int TCPClientSocketStarboard::Write(IOBuffer* buf, int buf_len, const CompletionCallback& callback) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(SbSocketIsValid(socket_)); DCHECK(!waiting_connect()); DCHECK(!waiting_write_); @@ -516,6 +546,7 @@ } void TCPClientSocketStarboard::DidCompleteWrite() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(waiting_write_); int nwrite = SbSocketSendTo(socket_, write_buf_->data(), write_buf_len_, NULL); @@ -541,6 +572,7 @@ } void TCPClientSocketStarboard::DoWriteCallback(int rv) { + TRACK_MEMORY_SCOPE("Network"); DCHECK_NE(rv, ERR_IO_PENDING); // since Run may result in Write being called, clear write_callback_ up front. @@ -550,22 +582,27 @@ } bool TCPClientSocketStarboard::SetReceiveBufferSize(int32 size) { + TRACK_MEMORY_SCOPE("Network"); return SbSocketSetReceiveBufferSize(socket_, size); } bool TCPClientSocketStarboard::SetSendBufferSize(int32 size) { + TRACK_MEMORY_SCOPE("Network"); return SbSocketSetSendBufferSize(socket_, size); } bool TCPClientSocketStarboard::SetKeepAlive(bool enable, int delay) { + TRACK_MEMORY_SCOPE("Network"); return SbSocketSetTcpKeepAlive(socket_, enable, delay); } bool TCPClientSocketStarboard::SetNoDelay(bool no_delay) { + TRACK_MEMORY_SCOPE("Network"); return SbSocketSetTcpNoDelay(socket_, no_delay); } bool TCPClientSocketStarboard::SetWindowScaling(bool enable) { + TRACK_MEMORY_SCOPE("Network"); return SbSocketSetTcpWindowScaling(socket_, enable); }
diff --git a/src/net/spdy/spdy_protocol.h b/src/net/spdy/spdy_protocol.h index 30daee3..ab3bf3d 100644 --- a/src/net/spdy/spdy_protocol.h +++ b/src/net/spdy/spdy_protocol.h
@@ -573,7 +573,7 @@ void set_flags(uint8 flags) { frame_->flags_length_.flags_[0] = flags; } uint32 length() const { - return ntohl(frame_->flags_length_.length_) & kLengthMask; + return base::NetToHost32(frame_->flags_length_.length_) & kLengthMask; } void set_length(uint32 length) { @@ -583,13 +583,13 @@ | Flags (8) | Length (24 bits) | +----------------------------------+ */ - frame_->flags_length_.length_ = htonl((length & kLengthMask) - | (flags() << 24)); + frame_->flags_length_.length_ = + base::HostToNet32((length & kLengthMask) | (flags() << 24)); } bool is_control_frame() const { - return (ntohs(frame_->control_.version_) & kControlFlagMask) == - kControlFlagMask; + return (base::NetToHost16(frame_->control_.version_) & kControlFlagMask) == + kControlFlagMask; } // The size of the SpdyFrameBlock structure. @@ -614,7 +614,7 @@ : SpdyFrame(data, owns_buffer) {} SpdyStreamId stream_id() const { - return ntohl(frame_->data_.stream_id_) & kStreamIdMask; + return base::NetToHost32(frame_->data_.stream_id_) & kStreamIdMask; } // Note that setting the stream id sets the control bit to false. @@ -622,7 +622,7 @@ // should always be set correctly. void set_stream_id(SpdyStreamId id) { DCHECK_EQ(0u, (id & ~kStreamIdMask)); - frame_->data_.stream_id_ = htonl(id & kStreamIdMask); + frame_->data_.stream_id_ = base::HostToNet32(id & kStreamIdMask); } // Returns the size of the SpdyFrameBlock structure. @@ -648,7 +648,7 @@ // frame. Does not guarantee that there are no errors. bool AppearsToBeAValidControlFrame() const { // Right now we only check if the frame has an out-of-bounds type. - uint16 type = ntohs(block()->control_.type_); + uint16 type = base::NetToHost16(block()->control_.type_); // NOOP is not a 'valid' control frame in SPDY/3 and beyond. return type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES && @@ -657,8 +657,8 @@ uint16 version() const { const int kVersionMask = 0x7fff; - return static_cast<uint16>( - ntohs((block()->control_.version_)) & kVersionMask); + return static_cast<uint16>(base::NetToHost16((block()->control_.version_)) & + kVersionMask); } void set_version(uint16 version) { @@ -668,11 +668,12 @@ +----------------------------------+ */ DCHECK_EQ(0U, version & kControlFlagMask); - mutable_block()->control_.version_ = htons(kControlFlagMask | version); + mutable_block()->control_.version_ = + base::HostToNet16(kControlFlagMask | version); } SpdyControlType type() const { - uint16 type = ntohs(block()->control_.type_); + uint16 type = base::NetToHost16(block()->control_.type_); LOG_IF(DFATAL, type < SYN_STREAM || type >= NUM_CONTROL_FRAME_TYPES) << "Invalid control frame type " << type; return static_cast<SpdyControlType>(type); @@ -680,7 +681,8 @@ void set_type(SpdyControlType type) { DCHECK(type >= SYN_STREAM && type < NUM_CONTROL_FRAME_TYPES); - mutable_block()->control_.type_ = htons(static_cast<uint16>(type)); + mutable_block()->control_.type_ = + base::HostToNet16(static_cast<uint16>(type)); } // Returns true if this control frame is of a type that has a header block, @@ -707,19 +709,20 @@ : SpdyControlFrame(data, owns_buffer) {} SpdyStreamId stream_id() const { - return ntohl(block()->stream_id_) & kStreamIdMask; + return base::NetToHost32(block()->stream_id_) & kStreamIdMask; } void set_stream_id(SpdyStreamId id) { - mutable_block()->stream_id_ = htonl(id & kStreamIdMask); + mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask); } SpdyStreamId associated_stream_id() const { - return ntohl(block()->associated_stream_id_) & kStreamIdMask; + return base::NetToHost32(block()->associated_stream_id_) & kStreamIdMask; } void set_associated_stream_id(SpdyStreamId id) { - mutable_block()->associated_stream_id_ = htonl(id & kStreamIdMask); + mutable_block()->associated_stream_id_ = + base::HostToNet32(id & kStreamIdMask); } SpdyPriority priority() const { @@ -774,11 +777,11 @@ : SpdyControlFrame(data, owns_buffer) {} SpdyStreamId stream_id() const { - return ntohl(block()->stream_id_) & kStreamIdMask; + return base::NetToHost32(block()->stream_id_) & kStreamIdMask; } void set_stream_id(SpdyStreamId id) { - mutable_block()->stream_id_ = htonl(id & kStreamIdMask); + mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask); } int header_block_len() const { @@ -821,23 +824,23 @@ : SpdyControlFrame(data, owns_buffer) {} SpdyStreamId stream_id() const { - return ntohl(block()->stream_id_) & kStreamIdMask; + return base::NetToHost32(block()->stream_id_) & kStreamIdMask; } void set_stream_id(SpdyStreamId id) { - mutable_block()->stream_id_ = htonl(id & kStreamIdMask); + mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask); } SpdyStatusCodes status() const { SpdyStatusCodes status = - static_cast<SpdyStatusCodes>(ntohl(block()->status_)); + static_cast<SpdyStatusCodes>(base::NetToHost32(block()->status_)); if (status < INVALID || status >= NUM_STATUS_CODES) { status = INVALID; } return status; } void set_status(SpdyStatusCodes status) { - mutable_block()->status_ = htonl(static_cast<uint32>(status)); + mutable_block()->status_ = base::HostToNet32(static_cast<uint32>(status)); } // Returns the size of the SpdyRstStreamControlFrameBlock structure. @@ -861,11 +864,11 @@ : SpdyControlFrame(data, owns_buffer) {} uint32 num_entries() const { - return ntohl(block()->num_entries_); + return base::NetToHost32(block()->num_entries_); } void set_num_entries(int val) { - mutable_block()->num_entries_ = htonl(static_cast<uint32>(val)); + mutable_block()->num_entries_ = base::HostToNet32(static_cast<uint32>(val)); } int header_block_len() const { @@ -896,12 +899,10 @@ SpdyPingControlFrame(char* data, bool owns_buffer) : SpdyControlFrame(data, owns_buffer) {} - uint32 unique_id() const { - return ntohl(block()->unique_id_); - } + uint32 unique_id() const { return base::NetToHost32(block()->unique_id_); } void set_unique_id(uint32 unique_id) { - mutable_block()->unique_id_ = htonl(unique_id); + mutable_block()->unique_id_ = base::HostToNet32(unique_id); } static size_t size() { return sizeof(SpdyPingControlFrameBlock); } @@ -941,7 +942,7 @@ : SpdyControlFrame(data, owns_buffer) {} SpdyStreamId last_accepted_stream_id() const { - return ntohl(block()->last_accepted_stream_id_) & kStreamIdMask; + return base::NetToHost32(block()->last_accepted_stream_id_) & kStreamIdMask; } SpdyGoAwayStatus status() const { @@ -949,7 +950,7 @@ LOG(DFATAL) << "Attempted to access status of SPDY 2 GOAWAY."; return GOAWAY_INVALID; } else { - uint32 status = ntohl(block()->status_); + uint32 status = base::NetToHost32(block()->status_); if (status >= GOAWAY_NUM_STATUS_CODES) { return GOAWAY_INVALID; } else { @@ -959,7 +960,8 @@ } void set_last_accepted_stream_id(SpdyStreamId id) { - mutable_block()->last_accepted_stream_id_ = htonl(id & kStreamIdMask); + mutable_block()->last_accepted_stream_id_ = + base::HostToNet32(id & kStreamIdMask); } static size_t size() { return sizeof(SpdyGoAwayControlFrameBlock); } @@ -982,11 +984,11 @@ : SpdyControlFrame(data, owns_buffer) {} SpdyStreamId stream_id() const { - return ntohl(block()->stream_id_) & kStreamIdMask; + return base::NetToHost32(block()->stream_id_) & kStreamIdMask; } void set_stream_id(SpdyStreamId id) { - mutable_block()->stream_id_ = htonl(id & kStreamIdMask); + mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask); } // The number of bytes in the header block beyond the frame header length. @@ -1030,19 +1032,19 @@ : SpdyControlFrame(data, owns_buffer) {} SpdyStreamId stream_id() const { - return ntohl(block()->stream_id_) & kStreamIdMask; + return base::NetToHost32(block()->stream_id_) & kStreamIdMask; } void set_stream_id(SpdyStreamId id) { - mutable_block()->stream_id_ = htonl(id & kStreamIdMask); + mutable_block()->stream_id_ = base::HostToNet32(id & kStreamIdMask); } uint32 delta_window_size() const { - return ntohl(block()->delta_window_size_); + return base::NetToHost32(block()->delta_window_size_); } void set_delta_window_size(uint32 delta_window_size) { - mutable_block()->delta_window_size_ = htonl(delta_window_size); + mutable_block()->delta_window_size_ = base::HostToNet32(delta_window_size); } // Returns the size of the SpdyWindowUpdateControlFrameBlock structure.
diff --git a/src/net/test/disabled_if_big_endian.h b/src/net/test/disabled_if_big_endian.h new file mode 100644 index 0000000..dfc0eed --- /dev/null +++ b/src/net/test/disabled_if_big_endian.h
@@ -0,0 +1,30 @@ +/* + * Copyright 2015 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 NET_TEST_DISABLED_IF_BIG_ENDIAN_H_ +#define NET_TEST_DISABLED_IF_BIG_ENDIAN_H_ + +#define DISABLED_IF_BIG_ENDIAN(Test) Test + +#if defined(OS_STARBOARD) +#include "starboard/configuration.h" +#if SB_IS(BIG_ENDIAN) +#undef DISABLED_IF_BIG_ENDIAN +#define DISABLED_IF_BIG_ENDIAN(Test) DISABLED_##Test +#endif // SB_IS(BIG_ENDIAN) +#endif // defined(OS_STARBOARD) + +#endif // NET_TEST_DISABLED_IF_BIG_ENDIAN_H_
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder.cc b/src/net/tools/dump_cache/url_to_filename_encoder.cc new file mode 100644 index 0000000..e928ee9 --- /dev/null +++ b/src/net/tools/dump_cache/url_to_filename_encoder.cc
@@ -0,0 +1,292 @@ +// Copyright (c) 2011 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 <stdlib.h> + +#include "base/logging.h" +#include "base/string_util.h" +#include "net/base/net_util.h" +#include "net/tools/dump_cache/url_to_filename_encoder.h" + +using std::string; + +namespace { + +// Returns 1 if buf is prefixed by "num_digits" of hex digits +// Teturns 0 otherwise. +// The function checks for '\0' for string termination. +int HexDigitsPrefix(const char* buf, int num_digits) { + for (int i = 0; i < num_digits; i++) { + if (!IsHexDigit(buf[i])) + return 0; // This also detects end of string as '\0' is not xdigit. + } + return 1; +} + +#if defined(WIN32) || defined(__LB_XB1__) || defined(__LB_XB360__) +#define strtoull _strtoui64 +#endif + +// A simple parser for long long values. Returns the parsed value if a +// valid integer is found; else returns deflt +// UInt64 and Int64 cannot handle decimal numbers with leading 0s. +uint64 ParseLeadingHex64Value(const char *str, uint64 deflt) { + char *error = NULL; + const uint64 value = strtoull(str, &error, 16); + return (error == str) ? deflt : value; +} + +} + +namespace net { + +// The escape character choice is made here -- all code and tests in this +// directory are based off of this constant. However, our testdata +// has tons of dependencies on this, so it cannot be changed without +// re-running those tests and fixing them. +const char UrlToFilenameEncoder::kEscapeChar = ','; +const char UrlToFilenameEncoder::kTruncationChar = '-'; +const size_t UrlToFilenameEncoder::kMaximumSubdirectoryLength = 128; + +void UrlToFilenameEncoder::AppendSegment(string* segment, string* dest) { + CHECK(!segment->empty()); + if ((*segment == ".") || (*segment == "..")) { + dest->append(1, kEscapeChar); + dest->append(*segment); + segment->clear(); + } else { + size_t segment_size = segment->size(); + if (segment_size > kMaximumSubdirectoryLength) { + // We need to inject ",-" at the end of the segment to signify that + // we are inserting an artificial '/'. This means we have to chop + // off at least two characters to make room. + segment_size = kMaximumSubdirectoryLength - 2; + + // But we don't want to break up an escape sequence that happens to lie at + // the end. Escape sequences are at most 2 characters. + if ((*segment)[segment_size - 1] == kEscapeChar) { + segment_size -= 1; + } else if ((*segment)[segment_size - 2] == kEscapeChar) { + segment_size -= 2; + } + dest->append(segment->data(), segment_size); + dest->append(1, kEscapeChar); + dest->append(1, kTruncationChar); + segment->erase(0, segment_size); + + // At this point, if we had segment_size=3, and segment="abcd", + // then after this erase, we will have written "abc,-" and set segment="d" + } else { + dest->append(*segment); + segment->clear(); + } + } +} + +void UrlToFilenameEncoder::EncodeSegment(const string& filename_prefix, + const string& escaped_ending, + char dir_separator, + string* encoded_filename) { + string filename_ending = UrlUtilities::Unescape(escaped_ending); + + char encoded[3]; + int encoded_len; + string segment; + + // TODO(jmarantz): This code would be a bit simpler if we disallowed + // Instaweb allowing filename_prefix to not end in "/". We could + // then change the is routine to just take one input string. + size_t start_of_segment = filename_prefix.find_last_of(dir_separator); + if (start_of_segment == string::npos) { + segment = filename_prefix; + } else { + segment = filename_prefix.substr(start_of_segment + 1); + *encoded_filename = filename_prefix.substr(0, start_of_segment + 1); + } + + size_t index = 0; + // Special case the first / to avoid adding a leading kEscapeChar. + if (!filename_ending.empty() && (filename_ending[0] == dir_separator)) { + encoded_filename->append(segment); + segment.clear(); + encoded_filename->append(1, dir_separator); + ++index; + } + + for (; index < filename_ending.length(); ++index) { + unsigned char ch = static_cast<unsigned char>(filename_ending[index]); + + // Note: instead of outputing an empty segment, we let the second slash + // be escaped below. + if ((ch == dir_separator) && !segment.empty()) { + AppendSegment(&segment, encoded_filename); + encoded_filename->append(1, dir_separator); + segment.clear(); + } else { + // After removing unsafe chars the only safe ones are _.=+- and alphanums. + if ((ch == '_') || (ch == '.') || (ch == '=') || (ch == '+') || + (ch == '-') || (('0' <= ch) && (ch <= '9')) || + (('A' <= ch) && (ch <= 'Z')) || (('a' <= ch) && (ch <= 'z'))) { + encoded[0] = ch; + encoded_len = 1; + } else { + encoded[0] = kEscapeChar; + encoded[1] = ch / 16; + encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0'; + encoded[2] = ch % 16; + encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0'; + encoded_len = 3; + } + segment.append(encoded, encoded_len); + + // If segment is too big, we must chop it into chunks. + if (segment.size() > kMaximumSubdirectoryLength) { + AppendSegment(&segment, encoded_filename); + encoded_filename->append(1, dir_separator); + } + } + } + + // Append "," to the leaf filename so the leaf can also be a branch., e.g. + // allow http://a/b/c and http://a/b/c/d to co-exist as files "/a/b/c," and + // /a/b/c/d". So we will rename the "d" here to "d,". If doing that pushed + // us over the 128 char limit, then we will need to append "/" and the + // remaining chars. + segment += kEscapeChar; + AppendSegment(&segment, encoded_filename); + if (!segment.empty()) { + // The last overflow segment is special, because we appended in + // kEscapeChar above. We won't need to check it again for size + // or further escaping. + encoded_filename->append(1, dir_separator); + encoded_filename->append(segment); + } +} + +// Note: this decoder is not the exact inverse of the EncodeSegment above, +// because it does not take into account a prefix. +bool UrlToFilenameEncoder::Decode(const string& encoded_filename, + char dir_separator, + string* decoded_url) { + enum State { + kStart, + kEscape, + kFirstDigit, + kTruncate, + kEscapeDot + }; + State state = kStart; + char hex_buffer[3]; + hex_buffer[2] = '\0'; + for (size_t i = 0; i < encoded_filename.size(); ++i) { + char ch = encoded_filename[i]; + switch (state) { + case kStart: + if (ch == kEscapeChar) { + state = kEscape; + } else if (ch == dir_separator) { + decoded_url->append(1, '/'); // URLs only use '/' not '\\' + } else { + decoded_url->append(1, ch); + } + break; + case kEscape: + if (HexDigitsPrefix(&ch, 1) == 1) { + hex_buffer[0] = ch; + state = kFirstDigit; + } else if (ch == kTruncationChar) { + state = kTruncate; + } else if (ch == '.') { + decoded_url->append(1, '.'); + state = kEscapeDot; // Look for at most one more dot. + } else if (ch == dir_separator) { + // Consider url "//x". This was once encoded to "/,/x,". + // This code is what skips the first Escape. + decoded_url->append(1, '/'); // URLs only use '/' not '\\' + state = kStart; + } else { + return false; + } + break; + case kFirstDigit: + if (HexDigitsPrefix(&ch, 1) == 1) { + hex_buffer[1] = ch; + uint64 hex_value = ParseLeadingHex64Value(hex_buffer, 0); + decoded_url->append(1, static_cast<char>(hex_value)); + state = kStart; + } else { + return false; + } + break; + case kTruncate: + if (ch == dir_separator) { + // Skip this separator, it was only put in to break up long + // path segments, but is not part of the URL. + state = kStart; + } else { + return false; + } + break; + case kEscapeDot: + decoded_url->append(1, ch); + state = kStart; + break; + } + } + + // All legal encoded filenames end in kEscapeChar. + return (state == kEscape); +} + +// Escape the given input |path| and chop any individual components +// of the path which are greater than kMaximumSubdirectoryLength characters +// into two chunks. +// +// This legacy version has several issues with aliasing of different URLs, +// inability to represent both /a/b/c and /a/b/c/d, and inability to decode +// the filenames back into URLs. +// +// But there is a large body of slurped data which depends on this format, +// so leave it as the default for spdy_in_mem_edsm_server. +string UrlToFilenameEncoder::LegacyEscape(const string& path) { + string output; + + // Note: We also chop paths into medium sized 'chunks'. + // This is due to the incompetence of the windows + // filesystem, which still hasn't figured out how + // to deal with long filenames. + int last_slash = 0; + for (size_t index = 0; index < path.length(); index++) { + char ch = path[index]; + if (ch == 0x5C) + last_slash = index; + if ((ch == 0x2D) || // hyphen + (ch == 0x5C) || (ch == 0x5F) || // backslash, underscore + ((0x30 <= ch) && (ch <= 0x39)) || // Digits [0-9] + ((0x41 <= ch) && (ch <= 0x5A)) || // Uppercase [A-Z] + ((0x61 <= ch) && (ch <= 0x7A))) { // Lowercase [a-z] + output.append(&path[index], 1); + } else { + char encoded[3]; + encoded[0] = 'x'; + encoded[1] = ch / 16; + encoded[1] += (encoded[1] >= 10) ? 'A' - 10 : '0'; + encoded[2] = ch % 16; + encoded[2] += (encoded[2] >= 10) ? 'A' - 10 : '0'; + output.append(encoded, 3); + } + if (index - last_slash > kMaximumSubdirectoryLength) { +#ifdef WIN32 + char slash = '\\'; +#else + char slash = '/'; +#endif + output.append(&slash, 1); + last_slash = index; + } + } + return output; +} + +} // namespace net
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder.h b/src/net/tools/dump_cache/url_to_filename_encoder.h new file mode 100644 index 0000000..b81a854 --- /dev/null +++ b/src/net/tools/dump_cache/url_to_filename_encoder.h
@@ -0,0 +1,211 @@ +// Copyright (c) 2010 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. + +// URL filename encoder goals: +// +// 1. Allow URLs with arbitrary path-segment length, generating filenames +// with a maximum of 128 characters. +// 2. Provide a somewhat human readable filenames, for easy debugging flow. +// 3. Provide reverse-mapping from filenames back to URLs. +// 4. Be able to distinguish http://x from http://x/ from http://x/index.html. +// Those can all be different URLs. +// 5. Be able to represent http://a/b/c and http://a/b/c/d, a pattern seen +// with Facebook Connect. +// +// We need an escape-character for representing characters that are legal +// in URL paths, but not in filenames, such as '?'. +// +// We can pick any legal character as an escape, as long as we escape it too. +// But as we have a goal of having filenames that humans can correlate with +// URLs, we should pick one that doesn't show up frequently in URLs. Candidates +// are ~`!@#$%^&()-=_+{}[],. but we would prefer to avoid characters that are +// shell escapes or that various build tools use. +// +// .#&%-=_+ occur frequently in URLs. +// <>:"/\|?* are illegal in Windows +// See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx +// ~`!$^&(){}[]'; are special to Unix shells +// In addition, build tools do not like ^@#% +// +// Josh took a quick look at the frequency of some special characters in +// Sadeesh's slurped directory from Fall 09 and found the following occurances: +// +// ^ 3 build tool doesn't like ^ in testdata filenames +// @ 10 build tool doesn't like @ in testdata filenames +// . 1676 too frequent in URLs +// , 76 THE WINNER +// # 0 build tool doesn't like it +// & 487 Prefer to avoid shell escapes +// % 374 g4 doesn't like it +// = 579 very frequent in URLs -- leave unmodified +// - 464 very frequent in URLs -- leave unmodified +// _ 798 very frequent in URLs -- leave unmodified +// +// +// The escaping algorithm is: +// 1) Escape all unfriendly symbols as ,XX where XX is the hex code. +// 2) Add a ',' at the end (We do not allow ',' at end of any directory name, +// so this assures that e.g. /a and /a/b can coexist in the filesystem). +// 3) Go through the path segment by segment (where a segment is one directory +// or leaf in the path) and +// 3a) If the segment is empty, escape the second slash. i.e. if it was +// www.foo.com//a then we escape the second / like www.foo.com/,2Fa, +// 3a) If it is "." or ".." prepend with ',' (so that we have a non- +// empty and non-reserved filename). +// 3b) If it is over 128 characters, break it up into smaller segments by +// inserting ,-/ (Windows limits paths to 128 chars, other OSes also +// have limits that would restrict us) +// +// For example: +// URL File +// / /, +// /index.html /index.html, +// /. /., +// /a/b /a/b, +// /a/b/ /a/b/, +// /a/b/c /a/b/c, Note: no prefix problem +// /u?foo=bar /u,3Ffoo=bar, +// // /,2F, +// /./ /,./, +// /../ /,../, +// /, /,2C, +// /,./ /,2C./, +// /very...longname/ /very...long,-/name If very...long is about 126 long. + +// NOTE: we avoid using some classes here (like FilePath and GURL) because we +// share this code with other projects externally. + +#ifndef NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_ +#define NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_ + +#include <string> + +#include "base/string_util.h" +#include "net/tools/dump_cache/url_utilities.h" + +namespace net { + +// Helper class for converting a URL into a filename. +class UrlToFilenameEncoder { + public: + // Given a |url| and a |base_path|, returns a filename which represents this + // |url|. |url| may include URL escaping such as %21 for ! + // |legacy_escape| indicates that this function should use the old-style + // of encoding. + // TODO(mbelshe): delete the legacy_escape code. + static std::string Encode(const std::string& url, std::string base_path, + bool legacy_escape) { + std::string filename; + if (!legacy_escape) { + std::string url_no_scheme = UrlUtilities::GetUrlHostPath(url); + EncodeSegment(base_path, url_no_scheme, '/', &filename); +#ifdef WIN32 + ReplaceAll(&filename, "/", "\\"); +#endif + } else { + std::string clean_url(url); + if (clean_url.length() && clean_url[clean_url.length()-1] == '/') + clean_url.append("index.html"); + + std::string host = UrlUtilities::GetUrlHost(clean_url); + filename.append(base_path); + filename.append(host); +#ifdef WIN32 + filename.append("\\"); +#else + filename.append("/"); +#endif + + std::string url_filename = UrlUtilities::GetUrlPath(clean_url); + // Strip the leading '/'. + if (url_filename[0] == '/') + url_filename = url_filename.substr(1); + + // Replace '/' with '\'. + ConvertToSlashes(&url_filename); + + // Strip double back-slashes ("\\\\"). + StripDoubleSlashes(&url_filename); + + // Save path as filesystem-safe characters. + url_filename = LegacyEscape(url_filename); + filename.append(url_filename); + +#ifndef WIN32 + // Last step - convert to native slashes. + const std::string slash("/"); + const std::string backslash("\\"); + ReplaceAll(&filename, backslash, slash); +#endif + } + + return filename; + } + + // Rewrite HTML in a form that the SPDY in-memory server + // can read. + // |filename_prefix| is prepended without escaping. + // |escaped_ending| is the URL to be encoded into a filename. It may have URL + // escaped characters (like %21 for !). + // |dir_separator| is "/" on Unix, "\" on Windows. + // |encoded_filename| is the resultant filename. + static void EncodeSegment( + const std::string& filename_prefix, + const std::string& escaped_ending, + char dir_separator, + std::string* encoded_filename); + + // Decodes a filename that was encoded with EncodeSegment, + // yielding back the original URL. + static bool Decode(const std::string& encoded_filename, + char dir_separator, + std::string* decoded_url); + + static const char kEscapeChar; + static const char kTruncationChar; + static const size_t kMaximumSubdirectoryLength; + + friend class UrlToFilenameEncoderTest; + + private: + // Appends a segment of the path, special-casing "." and "..", and + // ensuring that the segment does not exceed the path length. If it does, + // it chops the end off the segment, writes the segment with a separator of + // ",-/", and then rewrites segment to contain just the truncated piece so + // it can be used in the next iteration. + // |segment| is a read/write parameter containing segment to write + // Note: this should not be called with empty segment. + static void AppendSegment(std::string* segment, std::string* dest); + + // Allow reading of old slurped files. + static std::string LegacyEscape(const std::string& path); + + // Replace all instances of |from| within |str| as |to|. + static void ReplaceAll(std::string* str, const std::string& from, + const std::string& to) { + std::string::size_type pos(0); + while ((pos = str->find(from, pos)) != std::string::npos) { + str->replace(pos, from.size(), to); + pos += from.size(); + } + } + + // Replace all instances of "/" with "\" in |path|. + static void ConvertToSlashes(std::string* path) { + const std::string slash("/"); + const std::string backslash("\\"); + ReplaceAll(path, slash, backslash); + } + + // Replace all instances of "\\" with "%5C%5C" in |path|. + static void StripDoubleSlashes(std::string* path) { + const std::string doubleslash("\\\\"); + const std::string escaped_doubleslash("%5C%5C"); + ReplaceAll(path, doubleslash, escaped_doubleslash); + } +}; + +} // namespace net + +#endif // NET_TOOLS_DUMP_CACHE_URL_TO_FILENAME_ENCODER_H_
diff --git a/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc b/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc new file mode 100644 index 0000000..2e09e0b --- /dev/null +++ b/src/net/tools/dump_cache/url_to_filename_encoder_unittest.cc
@@ -0,0 +1,341 @@ +// Copyright (c) 2010 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 "net/tools/dump_cache/url_to_filename_encoder.h" + +#include <string> +#include <vector> + +#include "base/string_piece.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::StringPiece; +using std::string; + +namespace net { + +#ifdef WIN32 +char kDirSeparator = '\\'; +char kOtherDirSeparator = '/'; +#else +char kDirSeparator = '/'; +char kOtherDirSeparator = '\\'; +#endif + +class UrlToFilenameEncoderTest : public ::testing::Test { + protected: + UrlToFilenameEncoderTest() : escape_(1, UrlToFilenameEncoder::kEscapeChar), + dir_sep_(1, kDirSeparator) { + } + + void CheckSegmentLength(const StringPiece& escaped_word) { + std::vector<StringPiece> components; + Tokenize(escaped_word, StringPiece("/"), &components); + for (size_t i = 0; i < components.size(); ++i) { + EXPECT_GE(UrlToFilenameEncoder::kMaximumSubdirectoryLength, + components[i].size()); + } + } + + void CheckValidChars(const StringPiece& escaped_word, char invalid_slash) { + // These characters are invalid in Windows. We add in ', as that's pretty + // inconvenient in a Unix filename. + // + // See http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx + const string kInvalidChars = "<>:\"|?*'"; + for (size_t i = 0; i < escaped_word.size(); ++i) { + char c = escaped_word[i]; + EXPECT_EQ(string::npos, kInvalidChars.find(c)); + EXPECT_NE(invalid_slash, c); + EXPECT_NE('\0', c); // only invalid character in Posix + EXPECT_GT(0x7E, c); // only English printable characters + } + } + + void Validate(const string& in_word, const string& gold_word) { + string escaped_word, url; + UrlToFilenameEncoder::EncodeSegment("", in_word, '/', &escaped_word); + EXPECT_EQ(gold_word, escaped_word); + CheckSegmentLength(escaped_word); + CheckValidChars(escaped_word, '\\'); + UrlToFilenameEncoder::Decode(escaped_word, '/', &url); + EXPECT_EQ(in_word, url); + } + + void ValidateAllSegmentsSmall(const string& in_word) { + string escaped_word, url; + UrlToFilenameEncoder::EncodeSegment("", in_word, '/', &escaped_word); + CheckSegmentLength(escaped_word); + CheckValidChars(escaped_word, '\\'); + UrlToFilenameEncoder::Decode(escaped_word, '/', &url); + EXPECT_EQ(in_word, url); + } + + void ValidateNoChange(const string& word) { + // We always suffix the leaf with kEscapeChar, unless the leaf is empty. + Validate(word, word + escape_); + } + + void ValidateEscaped(unsigned char ch) { + // We always suffix the leaf with kEscapeChar, unless the leaf is empty. + char escaped[100]; + const char escape = UrlToFilenameEncoder::kEscapeChar; + base::snprintf(escaped, sizeof(escaped), "%c%02X%c", escape, ch, escape); + Validate(string(1, ch), escaped); + } + + void ValidateUrl(const string& url, const string& base_path, + bool legacy_escape, const string& gold_filename) { + string encoded_filename = UrlToFilenameEncoder::Encode( + url, base_path, legacy_escape); + EXPECT_EQ(gold_filename, encoded_filename); + if (!legacy_escape) { + CheckSegmentLength(encoded_filename); + CheckValidChars(encoded_filename, kOtherDirSeparator); + string decoded_url; + UrlToFilenameEncoder::Decode(encoded_filename, kDirSeparator, + &decoded_url); + if (url != decoded_url) { + EXPECT_EQ(url, "http://" + decoded_url); + } + } + } + + void ValidateUrlOldNew(const string& url, const string& gold_old_filename, + const string& gold_new_filename) { + ValidateUrl(url, "", true, gold_old_filename); + ValidateUrl(url, "", false, gold_new_filename); + } + + void ValidateEncodeSame(const string& url1, const string& url2) { + string filename1 = UrlToFilenameEncoder::Encode(url1, "", false); + string filename2 = UrlToFilenameEncoder::Encode(url2, "", false); + EXPECT_EQ(filename1, filename2); + } + + string escape_; + string dir_sep_; +}; + +TEST_F(UrlToFilenameEncoderTest, DoesNotEscape) { + ValidateNoChange(""); + ValidateNoChange("abcdefg"); + ValidateNoChange("abcdefghijklmnopqrstuvwxyz"); + ValidateNoChange("ZYXWVUT"); + ValidateNoChange("ZYXWVUTSRQPONMLKJIHGFEDCBA"); + ValidateNoChange("01234567689"); + ValidateNoChange("_.=+-"); + ValidateNoChange("abcdefghijklmnopqrstuvwxyzZYXWVUTSRQPONMLKJIHGFEDCBA" + "01234567689_.=+-"); + ValidateNoChange("index.html"); + ValidateNoChange("/"); + ValidateNoChange("/."); + ValidateNoChange("."); + ValidateNoChange(".."); +} + +TEST_F(UrlToFilenameEncoderTest, Escapes) { + const string bad_chars = + "<>:\"\\|?*" // Illegal on Windows + "~`!$^&(){}[]';" // Bad for Unix shells + "^@" // Build tool doesn't like + "#%" // Tool doesn't like + ","; // The escape char has to be escaped + + for (size_t i = 0; i < bad_chars.size(); ++i) { + ValidateEscaped(bad_chars[i]); + } + + // Check non-printable characters. + ValidateEscaped('\0'); + for (size_t i = 127; i < 256; ++i) { + ValidateEscaped(static_cast<char>(i)); + } +} + +TEST_F(UrlToFilenameEncoderTest, DoesEscapeCorrectly) { + Validate("mysite.com&x", "mysite.com" + escape_ + "26x" + escape_); + Validate("/./", "/" + escape_ + "./" + escape_); + Validate("/../", "/" + escape_ + "../" + escape_); + Validate("//", "/" + escape_ + "2F" + escape_); + Validate("/./leaf", "/" + escape_ + "./leaf" + escape_); + Validate("/../leaf", "/" + escape_ + "../leaf" + escape_); + Validate("//leaf", "/" + escape_ + "2Fleaf" + escape_); + Validate("mysite/u?param1=x¶m2=y", + "mysite/u" + escape_ + "3Fparam1=x" + escape_ + "26param2=y" + + escape_); + Validate("search?q=dogs&go=&form=QBLH&qs=n", // from Latency Labs bing test. + "search" + escape_ + "3Fq=dogs" + escape_ + "26go=" + escape_ + + "26form=QBLH" + escape_ + "26qs=n" + escape_); + Validate("~joebob/my_neeto-website+with_stuff.asp?id=138&content=true", + "" + escape_ + "7Ejoebob/my_neeto-website+with_stuff.asp" + escape_ + + "3Fid=138" + escape_ + "26content=true" + escape_); +} + +TEST_F(UrlToFilenameEncoderTest, EncodeUrlCorrectly) { + ValidateUrlOldNew("http://www.google.com/index.html", + "www.google.com" + dir_sep_ + "indexx2Ehtml", + "www.google.com" + dir_sep_ + "index.html" + escape_); + ValidateUrlOldNew("http://www.google.com/x/search?hl=en&q=dogs&oq=", + "www.google.com" + dir_sep_ + "x" + dir_sep_ + + "searchx3Fhlx3Denx26qx3Ddogsx26oqx3D", + + "www.google.com" + dir_sep_ + "x" + dir_sep_ + "search" + + escape_ + "3Fhl=en" + escape_ + "26q=dogs" + escape_ + + "26oq=" + escape_); + ValidateUrlOldNew("http://www.foo.com/a//", + "www.foo.com" + dir_sep_ + "ax255Cx255Cindexx2Ehtml", + "www.foo.com" + dir_sep_ + "a" + dir_sep_ + escape_ + "2F" + + escape_); + + // From bug: Double slash preserved. + ValidateUrl("http://www.foo.com/u?site=http://www.google.com/index.html", + "", false, + "www.foo.com" + dir_sep_ + "u" + escape_ + "3Fsite=http" + + escape_ + "3A" + dir_sep_ + escape_ + "2Fwww.google.com" + + dir_sep_ + "index.html" + escape_); + ValidateUrlOldNew( + "http://blogutils.net/olct/online.php?" + "site=http://thelwordfanfics.blogspot.&interval=600", + + "blogutils.net" + dir_sep_ + "olct" + dir_sep_ + "onlinex2Ephpx3F" + "sitex3Dhttpx3Ax255Cx255Cthelwordfanficsx2Eblogspotx2Ex26intervalx3D600", + + "blogutils.net" + dir_sep_ + "olct" + dir_sep_ + "online.php" + escape_ + + "3Fsite=http" + escape_ + "3A" + dir_sep_ + escape_ + + "2Fthelwordfanfics.blogspot." + escape_ + "26interval=600" + escape_); +} + +// From bug: Escapes treated the same as normal char. +TEST_F(UrlToFilenameEncoderTest, UnescapeUrlsBeforeEncode) { + for (int i = 0; i < 128; ++i) { + string unescaped(1, static_cast<char>(i)); + string escaped = base::StringPrintf("%%%02X", i); + ValidateEncodeSame(unescaped, escaped); + } + + ValidateEncodeSame( + "http://www.blogger.com/navbar.g?bName=God!&Mode=FOO&searchRoot" + "=http%3A%2F%2Fsurvivorscanthrive.blogspot.com%2Fsearch", + + "http://www.blogger.com/navbar.g?bName=God%21&Mode=FOO&searchRoot" + "=http%3A%2F%2Fsurvivorscanthrive.blogspot.com%2Fsearch"); +} + +// From bug: Filename encoding is not prefix-free. +TEST_F(UrlToFilenameEncoderTest, EscapeSecondSlash) { + Validate("/", "/" + escape_); + Validate("//", "/" + escape_ + "2F" + escape_); + Validate("///", "/" + escape_ + "2F" + "/" + escape_); +} + +TEST_F(UrlToFilenameEncoderTest, LongTail) { + static char long_word[] = + "~joebob/briggs/12345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890"; + + // the long lines in the string below are 64 characters, so we can see + // the slashes every 128. + string gold_long_word = + escape_ + "7Ejoebob/briggs/" + "1234567890123456789012345678901234567890123456789012345678901234" + "56789012345678901234567890123456789012345678901234567890123456" + + escape_ + "-/" + "7890123456789012345678901234567890123456789012345678901234567890" + "12345678901234567890123456789012345678901234567890123456789012" + + escape_ + "-/" + "3456789012345678901234567890123456789012345678901234567890123456" + "78901234567890123456789012345678901234567890123456789012345678" + + escape_ + "-/" + "9012345678901234567890" + escape_; + EXPECT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength, + sizeof(long_word)); + Validate(long_word, gold_long_word); +} + +TEST_F(UrlToFilenameEncoderTest, LongTailQuestion) { + // Here the '?' in the last path segment expands to @3F, making + // it hit 128 chars before the input segment gets that big. + static char long_word[] = + "~joebob/briggs/1234567?1234567?1234567?1234567?1234567?" + "1234567?1234567?1234567?1234567?1234567?1234567?1234567?" + "1234567?1234567?1234567?1234567?1234567?1234567?1234567?" + "1234567?1234567?1234567?1234567?1234567?1234567?1234567?" + "1234567?1234567?1234567?1234567?1234567?1234567?1234567?" + "1234567?1234567?1234567?1234567?1234567?1234567?1234567?"; + + // Notice that at the end of the third segment, we avoid splitting + // the (escape_ + "3F") that was generated from the "?", so that segment is + // only 127 characters. + string pattern = "1234567" + escape_ + "3F"; // 10 characters + string gold_long_word = + escape_ + "7Ejoebob/briggs/" + + pattern + pattern + pattern + pattern + pattern + pattern + "1234" + "567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern + + "123456" + escape_ + "-/" + "7" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern + + pattern + pattern + pattern + pattern + pattern + pattern + pattern + + "12" + + escape_ + "-/" + "34567" + escape_ + "3F" + pattern + pattern + pattern + pattern + pattern + + "1234567" + escape_ + "3F" + pattern + pattern + pattern + pattern + + pattern + "1234567" + + escape_ + "-/" + + escape_ + "3F" + pattern + pattern + escape_; + EXPECT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength, + sizeof(long_word)); + Validate(long_word, gold_long_word); +} + +TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenNoEscape) { + // hit corner cases, +/- 4 characters from kMaxLen + for (int i = -4; i <= 4; ++i) { + string input; + input.append(i + UrlToFilenameEncoder::kMaximumSubdirectoryLength, 'x'); + ValidateAllSegmentsSmall(input); + } +} + +TEST_F(UrlToFilenameEncoderTest, CornerCasesNearMaxLenWithEscape) { + // hit corner cases, +/- 4 characters from kMaxLen. This time we + // leave off the last 'x' and put in a '.', which ensures that we + // are truncating with '/' *after* the expansion. + for (int i = -4; i <= 4; ++i) { + string input; + input.append(i + UrlToFilenameEncoder::kMaximumSubdirectoryLength - 1, 'x'); + input.append(1, '.'); // this will expand to 3 characters. + ValidateAllSegmentsSmall(input); + } +} + +TEST_F(UrlToFilenameEncoderTest, LeafBranchAlias) { + Validate("/a/b/c", "/a/b/c" + escape_); // c is leaf file "c," + Validate("/a/b/c/d", "/a/b/c/d" + escape_); // c is directory "c" + Validate("/a/b/c/d/", "/a/b/c/d/" + escape_); +} + + +TEST_F(UrlToFilenameEncoderTest, BackslashSeparator) { + string long_word; + string escaped_word; + long_word.append(UrlToFilenameEncoder::kMaximumSubdirectoryLength + 1, 'x'); + UrlToFilenameEncoder::EncodeSegment("", long_word, '\\', &escaped_word); + + // check that one backslash, plus the escape ",-", and the ending , got added. + EXPECT_EQ(long_word.size() + 4, escaped_word.size()); + ASSERT_LT(UrlToFilenameEncoder::kMaximumSubdirectoryLength, + escaped_word.size()); + // Check that the backslash got inserted at the correct spot. + EXPECT_EQ('\\', escaped_word[ + UrlToFilenameEncoder::kMaximumSubdirectoryLength]); +} + +} // namespace net +
diff --git a/src/net/tools/dump_cache/url_utilities.cc b/src/net/tools/dump_cache/url_utilities.cc new file mode 100644 index 0000000..fe64bd9 --- /dev/null +++ b/src/net/tools/dump_cache/url_utilities.cc
@@ -0,0 +1,126 @@ +// Copyright (c) 2010 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 "net/tools/dump_cache/url_utilities.h" + +#include "base/logging.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" + +namespace net { + +std::string UrlUtilities::GetUrlHost(const std::string& url) { + size_t b = url.find("//"); + if (b == std::string::npos) + b = 0; + else + b += 2; + size_t next_slash = url.find_first_of('/', b); + size_t next_colon = url.find_first_of(':', b); + if (next_slash != std::string::npos + && next_colon != std::string::npos + && next_colon < next_slash) { + return std::string(url, b, next_colon - b); + } + if (next_slash == std::string::npos) { + if (next_colon != std::string::npos) { + return std::string(url, b, next_colon - b); + } else { + next_slash = url.size(); + } + } + return std::string(url, b, next_slash - b); +} + +std::string UrlUtilities::GetUrlHostPath(const std::string& url) { + size_t b = url.find("//"); + if (b == std::string::npos) + b = 0; + else + b += 2; + return std::string(url, b); +} + +std::string UrlUtilities::GetUrlPath(const std::string& url) { + size_t b = url.find("//"); + if (b == std::string::npos) + b = 0; + else + b += 2; + b = url.find("/", b); + if (b == std::string::npos) + return "/"; + + size_t e = url.find("#", b+1); + if (e != std::string::npos) + return std::string(url, b, (e - b)); + return std::string(url, b); +} + +namespace { + +// Parsing states for UrlUtilities::Unescape +enum UnescapeState { + NORMAL, // We are not in the middle of parsing an escape. + ESCAPE1, // We just parsed % . + ESCAPE2 // We just parsed %X for some hex digit X. +}; + +} // namespace + +std::string UrlUtilities::Unescape(const std::string& escaped_url) { + std::string unescaped_url, escape_text; + int escape_value; + UnescapeState state = NORMAL; + std::string::const_iterator iter = escaped_url.begin(); + while (iter < escaped_url.end()) { + char c = *iter; + switch (state) { + case NORMAL: + if (c == '%') { + escape_text.clear(); + state = ESCAPE1; + } else { + unescaped_url.push_back(c); + } + ++iter; + break; + case ESCAPE1: + if (IsHexDigit(c)) { + escape_text.push_back(c); + state = ESCAPE2; + ++iter; + } else { + // Unexpected, % followed by non-hex chars, pass it through. + unescaped_url.push_back('%'); + state = NORMAL; + } + break; + case ESCAPE2: + if (IsHexDigit(c)) { + escape_text.push_back(c); + bool ok = base::HexStringToInt(escape_text, &escape_value); + DCHECK(ok); + unescaped_url.push_back(static_cast<unsigned char>(escape_value)); + state = NORMAL; + ++iter; + } else { + // Unexpected, % followed by non-hex chars, pass it through. + unescaped_url.push_back('%'); + unescaped_url.append(escape_text); + state = NORMAL; + } + break; + } + } + // Unexpected, % followed by end of string, pass it through. + if (state == ESCAPE1 || state == ESCAPE2) { + unescaped_url.push_back('%'); + unescaped_url.append(escape_text); + } + return unescaped_url; +} + +} // namespace net +
diff --git a/src/net/tools/dump_cache/url_utilities.h b/src/net/tools/dump_cache/url_utilities.h new file mode 100644 index 0000000..c9d8ea5 --- /dev/null +++ b/src/net/tools/dump_cache/url_utilities.h
@@ -0,0 +1,35 @@ +// Copyright (c) 2010 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. + +#ifndef NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_ +#define NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_ + +#include <string> + +namespace net { + +struct UrlUtilities { + // Gets the host from an url, strips the port number as well if the url + // has one. + // For example: calling GetUrlHost(www.foo.com:8080/boo) returns www.foo.com + static std::string GetUrlHost(const std::string& url); + + // Get the host + path portion of an url + // e.g http://www.foo.com/path + // returns www.foo.com/path + static std::string GetUrlHostPath(const std::string& url); + + // Gets the path portion of an url. + // e.g http://www.foo.com/path + // returns /path + static std::string GetUrlPath(const std::string& url); + + // Unescape a url, converting all %XX to the the actual char 0xXX. + // For example, this will convert "foo%21bar" to "foo!bar". + static std::string Unescape(const std::string& escaped_url); +}; + +} // namespace net + +#endif // NET_TOOLS_DUMP_CACHE_URL_UTILITIES_H_
diff --git a/src/net/tools/dump_cache/url_utilities_unittest.cc b/src/net/tools/dump_cache/url_utilities_unittest.cc new file mode 100644 index 0000000..0f9cb06 --- /dev/null +++ b/src/net/tools/dump_cache/url_utilities_unittest.cc
@@ -0,0 +1,114 @@ +// Copyright (c) 2010 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 "net/tools/dump_cache/url_utilities.h" + +#include <string> + +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(UrlUtilitiesTest, GetUrlHost) { + EXPECT_EQ("www.foo.com", + UrlUtilities::GetUrlHost("http://www.foo.com")); + EXPECT_EQ("www.foo.com", + UrlUtilities::GetUrlHost("http://www.foo.com:80")); + EXPECT_EQ("www.foo.com", + UrlUtilities::GetUrlHost("http://www.foo.com:80/")); + EXPECT_EQ("www.foo.com", + UrlUtilities::GetUrlHost("http://www.foo.com/news")); + EXPECT_EQ("www.foo.com", + UrlUtilities::GetUrlHost("www.foo.com:80/news?q=hello")); + EXPECT_EQ("www.foo.com", + UrlUtilities::GetUrlHost("www.foo.com/news?q=a:b")); + EXPECT_EQ("www.foo.com", + UrlUtilities::GetUrlHost("www.foo.com:80")); +} + +TEST(UrlUtilitiesTest, GetUrlHostPath) { + EXPECT_EQ("www.foo.com", + UrlUtilities::GetUrlHostPath("http://www.foo.com")); + EXPECT_EQ("www.foo.com:80", + UrlUtilities::GetUrlHostPath("http://www.foo.com:80")); + EXPECT_EQ("www.foo.com:80/", + UrlUtilities::GetUrlHostPath("http://www.foo.com:80/")); + EXPECT_EQ("www.foo.com/news", + UrlUtilities::GetUrlHostPath("http://www.foo.com/news")); + EXPECT_EQ("www.foo.com:80/news?q=hello", + UrlUtilities::GetUrlHostPath("www.foo.com:80/news?q=hello")); + EXPECT_EQ("www.foo.com/news?q=a:b", + UrlUtilities::GetUrlHostPath("www.foo.com/news?q=a:b")); + EXPECT_EQ("www.foo.com:80", + UrlUtilities::GetUrlHostPath("www.foo.com:80")); +} + +TEST(UrlUtilitiesTest, GetUrlPath) { + EXPECT_EQ("/", + UrlUtilities::GetUrlPath("http://www.foo.com")); + EXPECT_EQ("/", + UrlUtilities::GetUrlPath("http://www.foo.com:80")); + EXPECT_EQ("/", + UrlUtilities::GetUrlPath("http://www.foo.com:80/")); + EXPECT_EQ("/news", + UrlUtilities::GetUrlPath("http://www.foo.com/news")); + EXPECT_EQ("/news?q=hello", + UrlUtilities::GetUrlPath("www.foo.com:80/news?q=hello")); + EXPECT_EQ("/news?q=a:b", + UrlUtilities::GetUrlPath("www.foo.com/news?q=a:b")); + EXPECT_EQ("/", + UrlUtilities::GetUrlPath("www.foo.com:80")); +} + +TEST(UrlUtilitiesTest, Unescape) { + // Basic examples are left alone. + EXPECT_EQ("http://www.foo.com", + UrlUtilities::Unescape("http://www.foo.com")); + EXPECT_EQ("www.foo.com:80/news?q=hello", + UrlUtilities::Unescape("www.foo.com:80/news?q=hello")); + + // All chars can be unescaped. + EXPECT_EQ("~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/", + UrlUtilities::Unescape("%7E%60%21%40%23%24%25%5E%26%2A%28%29%5F%2D" + "%2B%3D%7B%5B%7D%5D%7C%5C%3A%3B%22%27%3C%2C" + "%3E%2E%3F%2F")); + for (int c = 0; c < 256; ++c) { + std::string unescaped_char(1, implicit_cast<unsigned char>(c)); + std::string escaped_char = base::StringPrintf("%%%02X", c); + EXPECT_EQ(unescaped_char, UrlUtilities::Unescape(escaped_char)) + << "escaped_char = " << escaped_char; + escaped_char = base::StringPrintf("%%%02x", c); + EXPECT_EQ(unescaped_char, UrlUtilities::Unescape(escaped_char)) + << "escaped_char = " << escaped_char; + } + + // All non-% chars are left alone. + EXPECT_EQ("~`!@#$^&*()_-+={[}]|\\:;\"'<,>.?/", + UrlUtilities::Unescape("~`!@#$^&*()_-+={[}]|\\:;\"'<,>.?/")); + for (int c = 0; c < 256; ++c) { + if (c != '%') { + std::string just_char(1, implicit_cast<unsigned char>(c)); + EXPECT_EQ(just_char, UrlUtilities::Unescape(just_char)); + } + } + + // Some examples to unescape. + EXPECT_EQ("Hello, world!", UrlUtilities::Unescape("Hello%2C world%21")); + + // Not actually escapes. + EXPECT_EQ("%", UrlUtilities::Unescape("%")); + EXPECT_EQ("%www", UrlUtilities::Unescape("%www")); + EXPECT_EQ("%foo", UrlUtilities::Unescape("%foo")); + EXPECT_EQ("%1", UrlUtilities::Unescape("%1")); + EXPECT_EQ("%1x", UrlUtilities::Unescape("%1x")); + EXPECT_EQ("%%", UrlUtilities::Unescape("%%")); + // Escapes following non-escapes. + EXPECT_EQ("%!", UrlUtilities::Unescape("%%21")); + EXPECT_EQ("%2!", UrlUtilities::Unescape("%2%21")); +} + +} // namespace net +
diff --git a/src/net/udp/udp_listen_socket.cc b/src/net/udp/udp_listen_socket.cc index 848290b..82fdfd9 100644 --- a/src/net/udp/udp_listen_socket.cc +++ b/src/net/udp/udp_listen_socket.cc
@@ -33,6 +33,7 @@ #include "base/sys_byteorder.h" #include "base/threading/platform_thread.h" #include "build/build_config.h" +#include "nb/memory_scope.h" #include "net/base/net_util.h" #if defined(OS_STARBOARD) #include "starboard/socket.h" @@ -87,12 +88,14 @@ void UDPListenSocket::SendTo(const IPEndPoint& address, const std::string& str) { + TRACK_MEMORY_SCOPE("Network"); SendTo(address, str.data(), static_cast<int>(str.length())); } void UDPListenSocket::SendTo(const IPEndPoint& address, const char* bytes, int len) { + TRACK_MEMORY_SCOPE("Network"); #if defined(OS_STARBOARD) SbSocketAddress dst_addr; if (!address.ToSbSocketAddress(&dst_addr)) { @@ -130,6 +133,7 @@ } void UDPListenSocket::Read() { + TRACK_MEMORY_SCOPE("Network"); if (buffer_ == NULL) { // +1 for null termination buffer_.reset(new char[kUdpMaxPacketSize + 1]); @@ -192,6 +196,7 @@ } void UDPListenSocket::WatchSocket() { + TRACK_MEMORY_SCOPE("Network"); #if defined(OS_STARBOARD) MessageLoopForIO::current()->Watch( socket_, true, MessageLoopForIO::WATCH_READ, &watcher_, this);
diff --git a/src/net/udp/udp_listen_socket_unittest.cc b/src/net/udp/udp_listen_socket_unittest.cc index 4f3a077..dddabd2 100644 --- a/src/net/udp/udp_listen_socket_unittest.cc +++ b/src/net/udp/udp_listen_socket_unittest.cc
@@ -71,6 +71,7 @@ // Create another socket. SocketDescriptor cs = SbSocketCreate(sb_address.type, kSbSocketProtocolUdp); SbSocketSendTo(cs, data, size, &sb_address); + SbSocketDestroy(cs); #else // defined(OS_STARBOARD) // Get the watching address SockaddrStorage bind_addr;
diff --git a/src/net/url_request/url_fetcher_core.cc b/src/net/url_request/url_fetcher_core.cc index 2097903..82976da 100644 --- a/src/net/url_request/url_fetcher_core.cc +++ b/src/net/url_request/url_fetcher_core.cc
@@ -5,6 +5,7 @@ #include "net/url_request/url_fetcher_core.h" #include "base/bind.h" +#include "base/debug/trace_event.h" #include "base/file_util_proxy.h" #include "base/logging.h" #include "base/single_thread_task_runner.h" @@ -12,6 +13,7 @@ #include "base/stl_util.h" #include "base/thread_task_runner_handle.h" #include "base/tracked_objects.h" +#include "nb/memory_scope.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" #include "net/base/net_errors.h" @@ -41,6 +43,7 @@ URLFetcherCore::Registry::~Registry() {} void URLFetcherCore::Registry::AddURLFetcherCore(URLFetcherCore* core) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(!ContainsKey(fetchers_, core)); fetchers_.insert(core); } @@ -74,6 +77,7 @@ void URLFetcherCore::FileWriter::CreateFileAtPath( const FilePath& file_path) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); DCHECK(file_task_runner_.get()); base::FileUtilProxy::CreateOrOpen( @@ -86,6 +90,7 @@ } void URLFetcherCore::FileWriter::CreateTempFile() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); DCHECK(file_task_runner_.get()); base::FileUtilProxy::CreateTemporary( @@ -96,6 +101,7 @@ } void URLFetcherCore::FileWriter::WriteBuffer(int num_bytes) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); // Start writing to the file by setting the initial state @@ -109,6 +115,7 @@ void URLFetcherCore::FileWriter::ContinueWrite( base::PlatformFileError error_code, int bytes_written) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); if (file_handle_ == base::kInvalidPlatformFileValue) { @@ -153,6 +160,7 @@ } void URLFetcherCore::FileWriter::DisownFile() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); // Disowning is done by the delegate's OnURLFetchComplete method. @@ -164,6 +172,7 @@ } void URLFetcherCore::FileWriter::CloseFileAndCompleteRequest() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); if (file_handle_ != base::kInvalidPlatformFileValue) { @@ -176,6 +185,7 @@ } void URLFetcherCore::FileWriter::CloseAndDeleteFile() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); if (file_handle_ == base::kInvalidPlatformFileValue) { @@ -192,6 +202,7 @@ void URLFetcherCore::FileWriter::DeleteFile( base::PlatformFileError error_code) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); if (file_path_.empty()) return; @@ -208,6 +219,7 @@ base::PlatformFileError error_code, base::PassPlatformFile file_handle, bool created) { + TRACK_MEMORY_SCOPE("Network"); DidCreateFileInternal(file_path, error_code, file_handle); } @@ -215,6 +227,7 @@ base::PlatformFileError error_code, base::PassPlatformFile file_handle, const FilePath& file_path) { + TRACK_MEMORY_SCOPE("Network"); DidCreateFileInternal(file_path, error_code, file_handle); } @@ -222,6 +235,7 @@ const FilePath& file_path, base::PlatformFileError error_code, base::PassPlatformFile file_handle) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); if (base::PLATFORM_FILE_OK != error_code) { @@ -244,6 +258,7 @@ void URLFetcherCore::FileWriter::DidCloseFile( base::PlatformFileError error_code) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(core_->network_task_runner_->BelongsToCurrentThread()); if (base::PLATFORM_FILE_OK != error_code) { @@ -299,6 +314,7 @@ } void URLFetcherCore::Start() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(delegate_task_runner_); DCHECK(request_context_getter_) << "We need an URLRequestContext!"; if (network_task_runner_) { @@ -314,6 +330,7 @@ } void URLFetcherCore::Stop() { + TRACK_MEMORY_SCOPE("Network"); if (delegate_task_runner_) // May be NULL in tests. DCHECK(delegate_task_runner_->BelongsToCurrentThread()); @@ -522,6 +539,7 @@ bool URLFetcherCore::GetResponseAsFilePath(bool take_ownership, FilePath* out_response_path) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(delegate_task_runner_->BelongsToCurrentThread()); const bool destination_is_file = response_destination_ == URLFetcherCore::TEMP_FILE || @@ -542,6 +560,7 @@ void URLFetcherCore::OnReceivedRedirect(URLRequest* request, const GURL& new_url, bool* defer_redirect) { + TRACK_MEMORY_SCOPE("Network"); DCHECK_EQ(request, request_.get()); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (stop_on_redirect_) { @@ -555,6 +574,7 @@ } void URLFetcherCore::OnResponseStarted(URLRequest* request) { + TRACK_MEMORY_SCOPE("Network"); DCHECK_EQ(request, request_.get()); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (request_->status().is_success()) { @@ -592,6 +612,7 @@ void URLFetcherCore::OnReadCompleted(URLRequest* request, int bytes_read) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(request == request_); DCHECK(network_task_runner_->BelongsToCurrentThread()); #if !defined(COBALT) @@ -611,6 +632,10 @@ if (!request_->status().is_success() || bytes_read <= 0) break; + if (current_response_bytes_ == 0) { + TRACE_EVENT_ASYNC_STEP0("net::url_request", "URLFetcher", this, + "Fetch Content"); + } current_response_bytes_ += bytes_read; InformDelegateDownloadDataIfNecessary(bytes_read); @@ -709,7 +734,10 @@ } void URLFetcherCore::StartURLRequest() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(network_task_runner_->BelongsToCurrentThread()); + TRACE_EVENT_ASYNC_STEP0("net::url_request", "URLFetcher", this, + "Waiting For Data"); if (was_cancelled_) { // Since StartURLRequest() is posted as a *delayed* task, it may @@ -801,6 +829,9 @@ } void URLFetcherCore::StartURLRequestWhenAppropriate() { + TRACK_MEMORY_SCOPE("Network"); + TRACE_EVENT_ASYNC_BEGIN1("net::url_request", "URLFetcher", this, "url", + original_url_.path()); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (was_cancelled_) @@ -832,11 +863,13 @@ } void URLFetcherCore::CancelURLRequest() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (request_.get()) { request_->Cancel(); ReleaseRequest(); + TRACE_EVENT_ASYNC_END0("net::url_request", "URLFetcher", this); } // Release the reference to the request context. There could be multiple // references to URLFetcher::Core at this point so it may take a while to @@ -852,6 +885,9 @@ void URLFetcherCore::OnCompletedURLRequest( base::TimeDelta backoff_delay) { + TRACK_MEMORY_SCOPE("Network"); + TRACE_EVENT_ASYNC_END1("net::url_request", "URLFetcher", this, "url", + original_url_.path()); DCHECK(delegate_task_runner_->BelongsToCurrentThread()); // Save the status and backoff_delay so that delegates can read it. @@ -862,12 +898,14 @@ } void URLFetcherCore::InformDelegateFetchIsComplete() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(delegate_task_runner_->BelongsToCurrentThread()); if (delegate_) delegate_->OnURLFetchComplete(fetcher_); } void URLFetcherCore::NotifyMalformedContent() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (url_throttler_entry_ != NULL) { int status_code = response_code_; @@ -884,6 +922,7 @@ } void URLFetcherCore::RetryOrCompleteUrlFetch() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(network_task_runner_->BelongsToCurrentThread()); base::TimeDelta backoff_delay; @@ -953,6 +992,7 @@ } base::TimeTicks URLFetcherCore::GetBackoffReleaseTime() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (original_url_throttler_entry_) { @@ -974,6 +1014,7 @@ void URLFetcherCore::CompleteAddingUploadDataChunk( const std::string& content, bool is_last_chunk) { + TRACK_MEMORY_SCOPE("Network"); if (was_cancelled_) { // Since CompleteAddingUploadDataChunk() is posted as a *delayed* task, it // may run after the URLFetcher was already stopped. @@ -991,6 +1032,7 @@ // Return false if the write is pending, and the next read will // be done later. bool URLFetcherCore::WriteBuffer(int num_bytes) { + TRACK_MEMORY_SCOPE("Network"); bool write_complete = false; switch (response_destination_) { case STRING: @@ -1019,6 +1061,7 @@ } void URLFetcherCore::ReadResponse() { + TRACK_MEMORY_SCOPE("Network"); // Some servers may treat HEAD requests as GET requests. To free up the // network connection as soon as possible, signal that the request has // completed immediately, without trying to read any data back (all we care @@ -1057,6 +1100,7 @@ #endif // defined(COBALT) void URLFetcherCore::InformDelegateUploadProgress() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (request_.get()) { int64 current = request_->GetUploadProgress().position(); @@ -1076,12 +1120,14 @@ void URLFetcherCore::InformDelegateUploadProgressInDelegateThread( int64 current, int64 total) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(delegate_task_runner_->BelongsToCurrentThread()); if (delegate_) delegate_->OnURLFetchUploadProgress(fetcher_, current, total); } void URLFetcherCore::InformDelegateDownloadDataIfNecessary(int bytes_read) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (delegate_ && delegate_->ShouldSendDownloadData() && bytes_read != 0) { if (!download_data_cache_) { @@ -1102,6 +1148,7 @@ } void URLFetcherCore::InformDelegateDownloadData() { + TRACK_MEMORY_SCOPE("Network"); DCHECK(network_task_runner_->BelongsToCurrentThread()); if (delegate_ && delegate_->ShouldSendDownloadData() && download_data_cache_) { @@ -1114,6 +1161,7 @@ void URLFetcherCore::InformDelegateDownloadDataInDelegateThread( scoped_ptr<std::string> download_data) { + TRACK_MEMORY_SCOPE("Network"); DCHECK(delegate_task_runner_->BelongsToCurrentThread()); if (delegate_) { delegate_->OnURLFetchDownloadData(fetcher_, download_data.Pass());
diff --git a/src/net/url_request/url_request_data_job.cc b/src/net/url_request/url_request_data_job.cc index 5dffe32..e0c0cba 100644 --- a/src/net/url_request/url_request_data_job.cc +++ b/src/net/url_request/url_request_data_job.cc
@@ -6,6 +6,7 @@ #include "net/url_request/url_request_data_job.h" +#include "nb/memory_scope.h" #include "net/base/data_url.h" #include "net/base/net_errors.h" @@ -27,6 +28,7 @@ std::string* charset, std::string* data, const CompletionCallback& callback) const { + TRACK_MEMORY_SCOPE("Network"); // Check if data URL is valid. If not, don't bother to try to extract data. // Otherwise, parse the data from the data URL. const GURL& url = request_->url();
diff --git a/src/starboard/README.md b/src/starboard/README.md index 3d35ee3..5e81034 100644 --- a/src/starboard/README.md +++ b/src/starboard/README.md
@@ -160,7 +160,7 @@ In order to use a new platform configuration in a build, you need to ensure that you have a `gyp_configuration.py`, `gyp_configuration.gypi`, and -`starboard_platform.gypi` in their own directory for each binary variant, plus +`starboard_platform.gyp` in their own directory for each binary variant, plus the header files `configuration_public.h`, `atomic_public.h`, and `thread_types_public.h`. `gyp_cobalt` will scan your directories for these files, and then calculate a port name based on the directories between
diff --git a/src/starboard/atomic.h b/src/starboard/atomic.h index bdc3ad1..243c4ea 100644 --- a/src/starboard/atomic.h +++ b/src/starboard/atomic.h
@@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Module Overview: Starboard Atomic API +// // Defines a set of atomic integer operations that can be used as lightweight -// synchronization or as building blocks for heavier synchronization -// primitives. Their use is very subtle and requires detailed understanding of -// the behavior of supported architectures, so their direct use is not -// recommended except when rigorously deemed absolutely necessary for -// performance reasons. +// synchronization or as building blocks for heavier synchronization primitives. +// Their use is very subtle and requires detailed understanding of the behavior +// of supported architectures, so their direct use is not recommended except +// when rigorously deemed absolutely necessary for performance reasons. #ifndef STARBOARD_ATOMIC_H_ #define STARBOARD_ATOMIC_H_ @@ -41,23 +42,23 @@ // Always return the old value of "*ptr" // // This routine implies no memory barriers. -SbAtomic32 SbAtomicNoBarrier_CompareAndSwap(volatile SbAtomic32* ptr, - SbAtomic32 old_value, - SbAtomic32 new_value); +static SbAtomic32 SbAtomicNoBarrier_CompareAndSwap(volatile SbAtomic32* ptr, + SbAtomic32 old_value, + SbAtomic32 new_value); // Atomically store new_value into *ptr, returning the previous value held in // *ptr. This routine implies no memory barriers. -SbAtomic32 SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr, - SbAtomic32 new_value); +static SbAtomic32 SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr, + SbAtomic32 new_value); // Atomically increment *ptr by "increment". Returns the new value of // *ptr with the increment applied. This routine implies no memory barriers. -SbAtomic32 SbAtomicNoBarrier_Increment(volatile SbAtomic32* ptr, - SbAtomic32 increment); +static SbAtomic32 SbAtomicNoBarrier_Increment(volatile SbAtomic32* ptr, + SbAtomic32 increment); // Same as SbAtomicNoBarrier_Increment, but with a memory barrier. -SbAtomic32 SbAtomicBarrier_Increment(volatile SbAtomic32* ptr, - SbAtomic32 increment); +static SbAtomic32 SbAtomicBarrier_Increment(volatile SbAtomic32* ptr, + SbAtomic32 increment); // These following lower-level operations are typically useful only to people // implementing higher-level synchronization operations like spinlocks, mutexes, @@ -68,54 +69,55 @@ // after the operation. "Barrier" operations have both "Acquire" and "Release" // semantics. A SbAtomicMemoryBarrier() has "Barrier" semantics, but does no // memory access. -SbAtomic32 SbAtomicAcquire_CompareAndSwap(volatile SbAtomic32* ptr, - SbAtomic32 old_value, - SbAtomic32 new_value); -SbAtomic32 SbAtomicRelease_CompareAndSwap(volatile SbAtomic32* ptr, - SbAtomic32 old_value, - SbAtomic32 new_value); +static SbAtomic32 SbAtomicAcquire_CompareAndSwap(volatile SbAtomic32* ptr, + SbAtomic32 old_value, + SbAtomic32 new_value); +static SbAtomic32 SbAtomicRelease_CompareAndSwap(volatile SbAtomic32* ptr, + SbAtomic32 old_value, + SbAtomic32 new_value); -void SbAtomicMemoryBarrier(); -void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr, SbAtomic32 value); -void SbAtomicAcquire_Store(volatile SbAtomic32* ptr, SbAtomic32 value); -void SbAtomicRelease_Store(volatile SbAtomic32* ptr, SbAtomic32 value); +static void SbAtomicMemoryBarrier(); +static void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr, SbAtomic32 value); +static void SbAtomicAcquire_Store(volatile SbAtomic32* ptr, SbAtomic32 value); +static void SbAtomicRelease_Store(volatile SbAtomic32* ptr, SbAtomic32 value); -SbAtomic32 SbAtomicNoBarrier_Load(volatile const SbAtomic32* ptr); -SbAtomic32 SbAtomicAcquire_Load(volatile const SbAtomic32* ptr); -SbAtomic32 SbAtomicRelease_Load(volatile const SbAtomic32* ptr); +static SbAtomic32 SbAtomicNoBarrier_Load(volatile const SbAtomic32* ptr); +static SbAtomic32 SbAtomicAcquire_Load(volatile const SbAtomic32* ptr); +static SbAtomic32 SbAtomicRelease_Load(volatile const SbAtomic32* ptr); // 64-bit atomic operations (only available on 64-bit processors). #if SB_HAS(64_BIT_ATOMICS) typedef int64_t SbAtomic64; -SbAtomic64 SbAtomicNoBarrier_CompareAndSwap64(volatile SbAtomic64* ptr, - SbAtomic64 old_value, - SbAtomic64 new_value); -SbAtomic64 SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr, - SbAtomic64 new_value); -SbAtomic64 SbAtomicNoBarrier_Increment64(volatile SbAtomic64* ptr, - SbAtomic64 increment); -SbAtomic64 SbAtomicBarrier_Increment64(volatile SbAtomic64* ptr, - SbAtomic64 increment); -SbAtomic64 SbAtomicAcquire_CompareAndSwap64(volatile SbAtomic64* ptr, - SbAtomic64 old_value, - SbAtomic64 new_value); -SbAtomic64 SbAtomicRelease_CompareAndSwap64(volatile SbAtomic64* ptr, - SbAtomic64 old_value, - SbAtomic64 new_value); -void SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr, SbAtomic64 value); -void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr, SbAtomic64 value); -void SbAtomicRelease_Store64(volatile SbAtomic64* ptr, SbAtomic64 value); -SbAtomic64 SbAtomicNoBarrier_Load64(volatile const SbAtomic64* ptr); -SbAtomic64 SbAtomicAcquire_Load64(volatile const SbAtomic64* ptr); -SbAtomic64 SbAtomicRelease_Load64(volatile const SbAtomic64* ptr); +static SbAtomic64 SbAtomicNoBarrier_CompareAndSwap64(volatile SbAtomic64* ptr, + SbAtomic64 old_value, + SbAtomic64 new_value); +static SbAtomic64 SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr, + SbAtomic64 new_value); +static SbAtomic64 SbAtomicNoBarrier_Increment64(volatile SbAtomic64* ptr, + SbAtomic64 increment); +static SbAtomic64 SbAtomicBarrier_Increment64(volatile SbAtomic64* ptr, + SbAtomic64 increment); +static SbAtomic64 SbAtomicAcquire_CompareAndSwap64(volatile SbAtomic64* ptr, + SbAtomic64 old_value, + SbAtomic64 new_value); +static SbAtomic64 SbAtomicRelease_CompareAndSwap64(volatile SbAtomic64* ptr, + SbAtomic64 old_value, + SbAtomic64 new_value); +static void SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr, + SbAtomic64 value); +static void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr, SbAtomic64 value); +static void SbAtomicRelease_Store64(volatile SbAtomic64* ptr, SbAtomic64 value); +static SbAtomic64 SbAtomicNoBarrier_Load64(volatile const SbAtomic64* ptr); +static SbAtomic64 SbAtomicAcquire_Load64(volatile const SbAtomic64* ptr); +static SbAtomic64 SbAtomicRelease_Load64(volatile const SbAtomic64* ptr); #endif // SB_HAS(64_BIT_ATOMICS) // Pointer-sized atomic operations. Forwards to either 32-bit or 64-bit // functions as appropriate. typedef intptr_t SbAtomicPtr; -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicNoBarrier_CompareAndSwapPtr(volatile SbAtomicPtr* ptr, SbAtomicPtr old_value, SbAtomicPtr new_value) { @@ -126,7 +128,7 @@ #endif } -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicNoBarrier_ExchangePtr(volatile SbAtomicPtr* ptr, SbAtomicPtr new_value) { #if SB_HAS(64_BIT_POINTERS) @@ -136,7 +138,7 @@ #endif } -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicNoBarrier_IncrementPtr(volatile SbAtomicPtr* ptr, SbAtomicPtr increment) { #if SB_HAS(64_BIT_POINTERS) @@ -146,7 +148,7 @@ #endif } -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicBarrier_IncrementPtr(volatile SbAtomicPtr* ptr, SbAtomicPtr increment) { #if SB_HAS(64_BIT_POINTERS) return SbAtomicBarrier_Increment64(ptr, increment); @@ -155,7 +157,7 @@ #endif } -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicAcquire_CompareAndSwapPtr(volatile SbAtomicPtr* ptr, SbAtomicPtr old_value, SbAtomicPtr new_value) { @@ -166,7 +168,7 @@ #endif } -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicRelease_CompareAndSwapPtr(volatile SbAtomicPtr* ptr, SbAtomicPtr old_value, SbAtomicPtr new_value) { @@ -177,8 +179,8 @@ #endif } -SB_C_FORCE_INLINE void SbAtomicNoBarrier_StorePtr(volatile SbAtomicPtr* ptr, - SbAtomicPtr value) { +static SB_C_FORCE_INLINE void +SbAtomicNoBarrier_StorePtr(volatile SbAtomicPtr* ptr, SbAtomicPtr value) { #if SB_HAS(64_BIT_POINTERS) return SbAtomicNoBarrier_Store64(ptr, value); #else @@ -186,8 +188,8 @@ #endif } -SB_C_FORCE_INLINE void SbAtomicAcquire_StorePtr(volatile SbAtomicPtr* ptr, - SbAtomicPtr value) { +static SB_C_FORCE_INLINE void +SbAtomicAcquire_StorePtr(volatile SbAtomicPtr* ptr, SbAtomicPtr value) { #if SB_HAS(64_BIT_POINTERS) return SbAtomicAcquire_Store64(ptr, value); #else @@ -195,8 +197,8 @@ #endif } -SB_C_FORCE_INLINE void SbAtomicRelease_StorePtr(volatile SbAtomicPtr* ptr, - SbAtomicPtr value) { +static SB_C_FORCE_INLINE void +SbAtomicRelease_StorePtr(volatile SbAtomicPtr* ptr, SbAtomicPtr value) { #if SB_HAS(64_BIT_POINTERS) return SbAtomicRelease_Store64(ptr, value); #else @@ -204,7 +206,7 @@ #endif } -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicNoBarrier_LoadPtr(volatile const SbAtomicPtr* ptr) { #if SB_HAS(64_BIT_POINTERS) return SbAtomicNoBarrier_Load64(ptr); @@ -213,7 +215,7 @@ #endif } -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicAcquire_LoadPtr(volatile const SbAtomicPtr* ptr) { #if SB_HAS(64_BIT_POINTERS) return SbAtomicAcquire_Load64(ptr); @@ -222,7 +224,7 @@ #endif } -SB_C_FORCE_INLINE SbAtomicPtr +static SB_C_FORCE_INLINE SbAtomicPtr SbAtomicRelease_LoadPtr(volatile const SbAtomicPtr* ptr) { #if SB_HAS(64_BIT_POINTERS) return SbAtomicRelease_Load64(ptr);
diff --git a/src/starboard/audio_sink.h b/src/starboard/audio_sink.h index 59f507b..790741a 100644 --- a/src/starboard/audio_sink.h +++ b/src/starboard/audio_sink.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// An interface to output raw audio data. +// Module Overview: Starboard Audio Sink API +// +// Provides an interface to output raw audio data. #ifndef STARBOARD_AUDIO_SINK_H_ #define STARBOARD_AUDIO_SINK_H_ @@ -69,37 +71,49 @@ // --- Functions ------------------------------------------------------------- -// Returns whether the given audio sink handle is valid. +// Indicates whether the given audio sink handle is valid. +// +// |audio_sink|: The audio sink handle to check. SB_EXPORT bool SbAudioSinkIsValid(SbAudioSink audio_sink); // Creates an audio sink for the specified |channels| and -// |sampling_frequency_hz|, acquiring all resources needed to operate it, and -// returning an opaque handle to it. -// -// |frame_buffers| is an array of pointers to sample data. If the sink is -// operating in interleaved mode, the array contains only one element, which is -// an array containing |frames_per_channel| * |channels| samples. If the sink -// is operating in planar mode, the number of elements in the array will be the -// same as |channels|, each of the elements will be an array of -// |frames_per_channel| samples. The caller has to ensure that |frame_buffers| -// is valid until SbAudioSinkDestroy is called. -// -// |update_source_status_func| cannot be NULL. The audio sink will call it on -// an internal thread to query the status of the source. -// -// |consume_frames_func| cannot be NULL. The audio sink will call it on an -// internal thread to report consumed frames. -// -// |context| will be passed back into all callbacks, and is generally used to -// point at a class or struct that contains state associated with the audio -// sink. -// -// The audio sink will start to call |update_source_status_func| immediately -// after SbAudioSinkCreate is called, even before it returns. The caller has -// to ensure that the above callbacks returns meaningful values in this case. +// |sampling_frequency_hz|, acquires all resources needed to operate the +// audio sink, and returns an opaque handle to the audio sink. // // If the particular platform doesn't support the requested audio sink, the // function returns kSbAudioSinkInvalid without calling any of the callbacks. +// +// |channels|: The number of audio channels, such as left and right channels +// in stereo audio. +// |sampling_frequency_hz|: The sample frequency of the audio data being +// streamed. For example, 22,000 Hz means 22,000 sample elements represents +// one second of audio data. +// |audio_sample_type|: The type of each sample of the audio data -- +// |int16|, |float32|, etc. +// |audio_frame_storage_type|: Indicates whether frames are interleaved or +// planar. +// |frame_buffers|: An array of pointers to sample data. +// - If the sink is operating in interleaved mode, the array contains only +// one element, which is an array containing (|frames_per_channel| * +// |channels|) samples. +// - If the sink is operating in planar mode, the number of elements in the +// array is the same as |channels|, and each element is an array of +// |frames_per_channel| samples. The caller has to ensure that +// |frame_buffers| is valid until SbAudioSinkDestroy is called. +// |frames_per_channel|: The size of the frame buffers, in units of the +// number of samples per channel. The frame, in this case, represents a +// group of samples at the same media time, one for each channel. +// |update_source_status_func|: The audio sink calls this function on an +// internal thread to query the status of the source. The value cannot be NULL. +// |consume_frames_func|: The audio sink calls this function on an internal +// thread to report consumed frames. The value cannot be NULL. +// |context|: A value that is passed back to all callbacks and is generally +// used to point at a class or struct that contains state associated with the +// audio sink. +// |update_source_status_func|: A function that the audio sink starts to call +// immediately after SbAudioSinkCreate is called, even before it returns. +// The caller has to ensure that the callback functions above return +// meaningful values in this case. SB_EXPORT SbAudioSink SbAudioSinkCreate(int channels, int sampling_frequency_hz, @@ -111,32 +125,36 @@ SbAudioSinkConsumeFramesFunc consume_frames_func, void* context); -// Destroys |audio_sink|, freeing all associated resources. It will wait until -// all callbacks in progress is finished before returning. Upon returning of -// this function, no callbacks passed into SbAudioSinkCreate will be called -// further. This function can be called on any thread but cannot be called +// Destroys |audio_sink|, freeing all associated resources. Before +// returning, the function waits until all callbacks that are in progress +// have finished. After the function returns, no further calls are made +// callbacks passed into SbAudioSinkCreate. In addition, you can not pass +// |audio_sink| to any other SbAudioSink functions after SbAudioSinkDestroy +// has been called on it. +// +// This function can be called on any thread. However, it cannot be called // within any of the callbacks passed into SbAudioSinkCreate. -// It is not allowed to pass |audio_sink| into any other SbAudioSink function -// once SbAudioSinkDestroy has been called on it. +// +// |audio_sink|: The audio sink to destroy. SB_EXPORT void SbAudioSinkDestroy(SbAudioSink audio_sink); -// Returns the maximum channel supported on the platform. +// Returns the maximum number of channels supported on the platform. For +// example, the number would be |2| if the platform only supports stereo. SB_EXPORT int SbAudioSinkGetMaxChannels(); -// Returns the nearest supported sample rate of |sampling_frequency_hz|. On -// platforms that don't support all sample rates, it is the caller's +// Returns the supported sample rate closest to |sampling_frequency_hz|. +// On platforms that don't support all sample rates, it is the caller's // responsibility to resample the audio frames into the supported sample rate // returned by this function. SB_EXPORT int SbAudioSinkGetNearestSupportedSampleFrequency( int sampling_frequency_hz); -// Returns true if the particular SbMediaAudioSampleType is supported on this -// platform. +// Indicates whether |audio_sample_type| is supported on this platform. SB_EXPORT bool SbAudioSinkIsAudioSampleTypeSupported( SbMediaAudioSampleType audio_sample_type); -// Returns true if the particular SbMediaAudioFrameStorageType is supported on -// this platform. +// Indicates whether |audio_frame_storage_type| is supported on this +// platform. SB_EXPORT bool SbAudioSinkIsAudioFrameStorageTypeSupported( SbMediaAudioFrameStorageType audio_frame_storage_type);
diff --git a/src/starboard/blitter.h b/src/starboard/blitter.h index d183475..3ed8258 100644 --- a/src/starboard/blitter.h +++ b/src/starboard/blitter.h
@@ -12,14 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Starboard Blitter API. The Blitter API provides support for issuing simple -// blit-style draw commands to either an offscreen surface or to a Starboard -// SbWindow object. This API is designed to allow implementations make use -// of GPU hardware acceleration, if it is available. Draw commands exist for -// solid-color rectangles and rasterization/blitting of rectangular images onto +// Module Overview: Starboard Blitter API +// +// The Blitter API provides support for issuing simple blit-style draw +// commands to either an offscreen surface or to a Starboard SbWindow object. +// Blitter is jargon that means "BLock Transfer," which might be abbreviated +// as BLT and, hence, the word "blit." +// +// This API is designed to allow implementations make use of GPU hardware +// acceleration, if it is available. Draw commands exist for solid-color +// rectangles and rasterization/blitting of rectangular images onto // rectangular target patches. - -// Threading Concerns: +// +// #### Threading Concerns +// // Note that in general the Blitter API is not thread safe, except for all // SbBlitterDevice-related functions. All functions that are not required to // internally ensure any thread safety guarantees are prefaced with a comment @@ -97,12 +103,16 @@ #define kSbBlitterInvalidSurface ((SbBlitterSurface)NULL) // SbBlitterContext objects represent a stateful communications channel with -// a device. All state changes and draw calls will be made through a specific -// SbBlitterContext object. Every draw call made on a SbBlitterContext will -// be submitted to the device with the SbBlitterContext's current state applied -// to it. Draw calls may be submitted to the device as they are made on the -// SbBlitterContext, however they are not guaranteed to be submitted until -// the SbBlitterContext object is flushed. +// a device. All state changes and draw calls are made through a specific +// SbBlitterContext object. Every draw call made on a SbBlitterContext is +// submitted to the device with the SbBlitterContext's current state applied +// to it. +// +// Draw calls may be submitted to the device as they are made on the +// SbBlitterContext. However, they are not guaranteed to be submitted until +// the SbBlitterContext object is flushed. That is, until you call +// SbBlitterFlushContext, you are not guaranteed that any API calls you have +// made have been received or acted on by the graphics device. typedef struct SbBlitterContextPrivate SbBlitterContextPrivate; typedef SbBlitterContextPrivate* SbBlitterContext; #define kSbBlitterInvalidContext ((SbBlitterContext)NULL) @@ -283,66 +293,84 @@ return context != kSbBlitterInvalidContext; } -// Creates and returns a SbBlitterDevice object based on the Blitter API -// implementation's decision of which device should be default. The -// SbBlitterDevice object represents a connection to a device (like a GPU). On -// many platforms there is always one single obvious choice for a device to use, -// and that is the one that this function will return. For example, if this is -// called on a platform that has a single GPU, a device representing that GPU -// should be returned by this call. On a platform that has no GPU, a device -// representing a software CPU implementation may be returned. Only one -// default device can exist within a process at a time. -// This function is thread safe. +// Creates and returns an SbBlitterDevice based on the Blitter API +// implementation's decision of which device should be the default. The +// returned SbBlitterDevice represents a connection to a device (like a GPU). +// +// On many platforms there is always one single obvious choice for a device +// to use, and that is the one that this function will return. For example, +// if this is called on a platform that has a single GPU, this call should +// return an object that represents that GPU. On a platform that has no GPU, +// an object representing a software CPU implementation may be returned. +// +// Only one default device can exist within a process at a time. +// This function is thread-safe. // Returns kSbBlitterInvalidDevice on failure. SB_EXPORT SbBlitterDevice SbBlitterCreateDefaultDevice(); // Destroys |device|, cleaning up all resources associated with it. -// Returns whether the destruction succeeded. -// This function is thread safe, though of course it should not be called if -// |device| is still being accessed elsewhere. +// This function is thread-safe, but it should not be called if |device| is +// still being accessed elsewhere. +// +// The return value indicates whether the destruction succeeded. +// +// |device|: The SbBlitterDevice object to be destroyed. SB_EXPORT bool SbBlitterDestroyDevice(SbBlitterDevice device); -// Creates and returns a SbBlitterSwapChain object that can be used to send -// graphics to the display. By calling this function, |device| will be linked -// to |window|'s output and drawing to the returned swap chain will result in -// |device| being used to render to |window|. kSbBlitterInvalidSwapChain is -// returned on failure. +// Creates and returns an SbBlitterSwapChain that can then be used to send +// graphics to the display. This function links |device| to |window|'s output, +// and drawing to the returned swap chain will result in |device| being used +// to render to |window|. +// // This function must be called from the thread that called SbWindowCreate() // to create |window|. +// +// Returns kSbBlitterInvalidSwapChain on failure. SB_EXPORT SbBlitterSwapChain SbBlitterCreateSwapChainFromWindow(SbBlitterDevice device, SbWindow window); // Destroys |swap_chain|, cleaning up all resources associated with it. -// This function must be called on the same thread that called -// SbBlitterCreateSwapChainFromWindow(). -// This function is not thread safe. -// Returns the destruction succeeded. +// This function is not thread-safe and must be called on the same thread +// that called SbBlitterCreateSwapChainFromWindow(). +// +// The return value indicates whether the destruction succeeded. +// +// |swap_chain|: The SbBlitterSwapChain to be destroyed. SB_EXPORT bool SbBlitterDestroySwapChain(SbBlitterSwapChain swap_chain); // Returns the |SbBlitterRenderTarget| object that is owned by |swap_chain|. // The returned object can be used to provide a target to blitter draw calls -// wishing to draw directly to the display buffer. -// This function is not thread safe. -// kSbBlitterInvalidRenderTarget is returned on failure. +// that draw directly to the display buffer. This function is not thread-safe. +// +// Returns kSbBlitterInvalidRenderTarget on failure. +// +// |swap_chain|: The SbBlitterSwapChain for which the target object is being +// retrieved. SB_EXPORT SbBlitterRenderTarget SbBlitterGetRenderTargetFromSwapChain(SbBlitterSwapChain swap_chain); -// Returns whether |device| supports calls to SbBlitterCreatePixelData() -// with |pixel_format|. -// This function is thread safe. +// Indicates whether |device| supports calls to SbBlitterCreatePixelData +// with the specified |pixel_format|. This function is thread-safe. +// +// |device|: The device for which compatibility is being checked. +// |pixel_format|: The SbBlitterPixelDataFormat for which compatibility is +// being checked. SB_EXPORT bool SbBlitterIsPixelFormatSupportedByPixelData( SbBlitterDevice device, SbBlitterPixelDataFormat pixel_format); -// Allocates a SbBlitterPixelData object through |device| with |width|, |height| -// and |pixel_format|. |pixel_format| must be supported by |device| (see -// SbBlitterIsPixelFormatSupportedByPixelData()). Calling this function will -// result in the allocation of CPU-accessible (though perhaps -// blitter-device-resident) memory to store pixel data of the requested -// size/format. A SbBlitterPixelData object should either eventually be passed -// into a call to SbBlitterCreateSurfaceFromPixelData(), or passed into a call -// to SbBlitterDestroyPixelData(). -// This function is thread safe. +// Allocates an SbBlitterPixelData object through |device| with |width|, +// |height| and |pixel_format|. |pixel_format| must be supported by |device| +// (see SbBlitterIsPixelFormatSupportedByPixelData()). This function is +// thread-safe. +// +// Calling this function results in the allocation of CPU-accessible +// (though perhaps blitter-device-resident) memory to store pixel data +// of the requested size/format. An SbBlitterPixelData object should +// eventually be passed either into a call to +// SbBlitterCreateSurfaceFromPixelData() or into a call to +// SbBlitterDestroyPixelData(). +// // Returns kSbBlitterInvalidPixelData upon failure. SB_EXPORT SbBlitterPixelData SbBlitterCreatePixelData(SbBlitterDevice device, @@ -350,57 +378,74 @@ int height, SbBlitterPixelDataFormat pixel_format); -// Destroys the |pixel_data| object. Note that -// if SbBlitterCreateSurfaceFromPixelData() has been called on |pixel_data| -// before, this function does not need to be and should not be called. -// This function is thread safe. -// Returns whether the destruction succeeded. +// Destroys |pixel_data|. Note that this function does not need to be called +// and should not be called if SbBlitterCreateSurfaceFromPixelData() has been +// called on |pixel_data| before. This function is thread-safe. +// +// The return value indicates whether the destruction succeeded. +// +// |pixel_data|: The object to be destroyed. SB_EXPORT bool SbBlitterDestroyPixelData(SbBlitterPixelData pixel_data); -// Getter method to return the pitch (in bytes) for |pixel_data|. This -// indicates the number of bytes per row of pixel data in the image. -1 is -// returned in case of an error. -// This function is not thread safe. +// Retrieves the pitch (in bytes) for |pixel_data|. This indicates the number of +// bytes per row of pixel data in the image. This function is not thread-safe. +// +// Returns |-1| in the event of an error. +// +// |pixel_data|: The object for which you are retrieving the pitch. SB_EXPORT int SbBlitterGetPixelDataPitchInBytes(SbBlitterPixelData pixel_data); -// Getter method to return a CPU-accessible pointer to the pixel data -// represented by |pixel_data|. This pixel data can be modified by the CPU -// in order to initialize it on the CPU before calling -// SbBlitterCreateSurfaceFromPixelData(). Note that the pointe returned here -// is valid as long as |pixel_data| is valid, i.e. until either +// Retrieves a CPU-accessible pointer to the pixel data represented by +// |pixel_data|. This pixel data can be modified by the CPU to initialize it +// on the CPU before calling SbBlitterCreateSurfaceFromPixelData(). +// +// Note that the pointer returned here is valid as long as |pixel_data| is +// valid, which means it is valid until either // SbBlitterCreateSurfaceFromPixelData() or SbBlitterDestroyPixelData() is // called. -// This function is not thread safe. -// If there is an error, NULL is returned. +// +// This function is not thread-safe. +// +// Returns |NULL| in the event of an error. SB_EXPORT void* SbBlitterGetPixelDataPointer(SbBlitterPixelData pixel_data); -// Creates a SbBlitterSurface object on |device| (which must match the device -// used to create the input SbBlitterPixelData object, |pixel_format|). -// This function will destroy the input |pixel_data| object and so -// |pixel_data| should not be accessed again after this function is called. -// The returned surface cannot be used as a render target (e.g. calling +// Creates an SbBlitterSurface object on |device|. Note that |device| must +// match the device that was used to create the SbBlitterPixelData object +// provided via the |pixel_data| parameter. +// +// This function also destroys the input |pixel_data| object. As a result, +// |pixel_data| should not be accessed again after a call to this function. +// +// The returned object cannot be used as a render target (e.g. calling // SbBlitterGetRenderTargetFromSurface() on it will return // SbBlitterInvalidRenderTarget). -// This function is thread safe with respect to |device|, however -// |pixel_data| should not be modified on another thread while this function -// is called. -// kSbBlitterInvalidSurface is returned if there was an error. +// +// This function is thread-safe with respect to |device|, but |pixel_data| +// should not be modified on another thread while this function is called. +// +// Returns kSbBlitterInvalidSurface in the event of an error. SB_EXPORT SbBlitterSurface SbBlitterCreateSurfaceFromPixelData(SbBlitterDevice device, SbBlitterPixelData pixel_data); -// Returns whether the |device| supports calls to -// SbBlitterCreateRenderTargetSurface() with |pixel_format|. -// This function is thread safe. +// Indicates whether the |device| supports calls to +// SbBlitterCreateRenderTargetSurface() with |surface_format|. +// +// This function is thread-safe. +// +// |device|: The device being checked for compatibility. +// |surface_format|: The surface format being checked for compatibility. SB_EXPORT bool SbBlitterIsSurfaceFormatSupportedByRenderTargetSurface( SbBlitterDevice device, SbBlitterSurfaceFormat surface_format); // Creates a new surface with undefined pixel data on |device| with the -// specified |width|, |height| and |pixel_format|. One can set the pixel data +// specified |width|, |height| and |surface_format|. One can set the pixel data // on the resulting surface by getting its associated SbBlitterRenderTarget -// object by calling SbBlitterGetRenderTargetFromSurface(). -// This function is thread safe. +// object and then calling SbBlitterGetRenderTargetFromSurface(). +// +// This function is thread-safe. +// // Returns kSbBlitterInvalidSurface upon failure. SB_EXPORT SbBlitterSurface SbBlitterCreateRenderTargetSurface(SbBlitterDevice device, @@ -408,195 +453,277 @@ int height, SbBlitterSurfaceFormat surface_format); -// Destroys |surface|, cleaning up all resources associated with it. +// Destroys the |surface| object, cleaning up all resources associated with it. // This function is not thread safe. -// Returns whether the destruction succeeded. +// +// The return value indicates whether the destruction succeeded. +// +// |surface|: The object to be destroyed. SB_EXPORT bool SbBlitterDestroySurface(SbBlitterSurface surface); // Returns the SbBlitterRenderTarget object owned by |surface|. The returned // object can be used as a target for draw calls. -// This function is not thread safe. -// Returns kSbBlitterInvalidRenderTarget if |surface| is not able to provide a -// render target, or on any other error. +// +// This function returns kSbBlitterInvalidRenderTarget if |surface| is not +// able to provide a render target or on any other error. +// +// This function is not thread-safe. SB_EXPORT SbBlitterRenderTarget SbBlitterGetRenderTargetFromSurface(SbBlitterSurface surface); -// Returns a SbBlitterSurfaceInfo structure describing immutable parameters of -// |surface|, such as width, height and pixel format. The results will be -// set on the output parameter |surface_info| which cannot be NULL. -// This function is not thread safe. -// Returns whether the information was retrieved successfully. +// Retrieves an SbBlitterSurfaceInfo structure, which describes immutable +// parameters of the |surface|, such as its width, height and pixel format. +// The results are set on the output parameter |surface_info|, which cannot +// be NULL. +// +// The return value indicates whether the information was retrieved +// successfully. +// +// This function is not thread-safe. SB_EXPORT bool SbBlitterGetSurfaceInfo(SbBlitterSurface surface, SbBlitterSurfaceInfo* surface_info); -// Returns whether the combination of parameters (|surface|, |pixel_format|) are -// valid for calls to SbBlitterDownloadSurfacePixels(). -// This function is not thread safe. +// Indicates whether the combination of parameter values is valid for calls +// to SbBlitterDownloadSurfacePixels(). +// +// This function is not thread-safe. +// +// |surface|: The surface being checked. +// |pixel_format|: The pixel format that would be used on the surface. SB_EXPORT bool SbBlitterIsPixelFormatSupportedByDownloadSurfacePixels( SbBlitterSurface surface, SbBlitterPixelDataFormat pixel_format); // Downloads |surface| pixel data into CPU memory pointed to by // |out_pixel_data|, formatted according to the requested |pixel_format| and -// the requested |pitch_in_bytes|. Thus, |out_pixel_data| must point to a -// region of memory with a size of surface_height * |pitch_in_bytes| * -// SbBlitterBytesPerPixelForFormat(pixel_format) bytes. The function -// SbBlitterIsPixelFormatSupportedByDownloadSurfacePixels() can be called first -// to check that your requested |pixel_format| is valid for |surface|. When -// this function is called, it will first wait for all previously flushed +// the requested |pitch_in_bytes|. Before calling this function, you can call +// SbBlitterIsPixelFormatSupportedByDownloadSurfacePixels() to confirm that +// |pixel_format| is, in fact, valid for |surface|. +// +// When this function is called, it first waits for all previously flushed // graphics commands to be executed by the device before downloading the data. // Since this function waits for the pipeline to empty, it should be used -// sparingly, such as within in debug or test environments. The returned -// alpha format will be premultiplied. -// This function is not thread safe. -// Returns whether the pixel data was downloaded successfully or not. +// sparingly, such as within in debug or test environments. +// +// The return value indicates whether the pixel data was downloaded +// successfully. +// +// The returned alpha format is premultiplied. +// +// This function is not thread-safe. +// +// |out_pixel_data|: A pointer to a region of memory with a size of +// surface_height * |pitch_in_bytes| bytes. SB_EXPORT bool SbBlitterDownloadSurfacePixels( SbBlitterSurface surface, SbBlitterPixelDataFormat pixel_format, int pitch_in_bytes, void* out_pixel_data); -// Flips |swap_chain|, making the buffer that was previously accessible to -// draw commands via SbBlitterGetRenderTargetFromSwapChain() now visible on the -// display, while another buffer in an initially undefined state is setup as the -// draw command target. Note that you do not need to call +// Flips the |swap_chain| by making the buffer previously accessible to +// draw commands via SbBlitterGetRenderTargetFromSwapChain() visible on the +// display, while another buffer in an initially undefined state is set up +// as the new draw command target. Note that you do not need to call // SbBlitterGetRenderTargetFromSwapChain() again after flipping, the swap -// chain's render target will always refer to its current back buffer. This -// function will stall the calling thread until the next vertical refresh. -// Note that to ensure consistency with the Starboard Player API when rendering -// punch-out video, calls to SbPlayerSetBounds() will not take effect until -// this method is called. -// This function is not thread safe. -// Returns whether the flip succeeded. +// chain's render target always refers to its current back buffer. +// +// This function stalls the calling thread until the next vertical refresh. +// In addition, to ensure consistency with the Starboard Player API when +// rendering punch-out video, calls to SbPlayerSetBounds() do not take effect +// until this method is called. +// +// The return value indicates whether the flip succeeded. +// +// This function is not thread-safe. +// +// |swap_chain|: The SbBlitterSwapChain to be flipped. SB_EXPORT bool SbBlitterFlipSwapChain(SbBlitterSwapChain swap_chain); -// Returns the maximum number of contexts that |device| can support in parallel. -// In many cases, devices support only a single context. If -// SbBlitterCreateContext() has been used to create the maximum number of -// contexts, all subsequent calls to SbBlitterCreateContext() will fail. -// This function is thread safe. -// This function returns -1 upon failure. +// Returns the maximum number of contexts that |device| can support in +// parallel. Note that devices often support only a single context. +// +// This function is thread-safe. +// +// This function returns |-1| upon failure. +// +// |device|: The SbBlitterDevice for which the maximum number of contexts is +// returned. SB_EXPORT int SbBlitterGetMaxContexts(SbBlitterDevice device); -// Creates a SbBlitterContext object on the specified |device|. The returned -// context can be used to setup draw state and issue draw calls. Note that -// there is a limit on the number of contexts that can exist at the same time -// (in many cases this is 1), and this can be queried by calling -// SbBlitterGetMaxContexts(). SbBlitterContext objects keep track of draw -// state between a series of draw calls. Please refer to the documentation -// around the definition of SbBlitterContext for more information about -// contexts. -// This function is thread safe. -// This function returns kSbBlitterInvalidContext upon failure. +// Creates an SbBlitterContext object on |device|. The returned context can be +// used to set up draw state and issue draw calls. +// +// Note that there is a limit on the number of contexts that can exist +// simultaneously, which can be queried by calling SbBlitterGetMaxContexts(). +// (The limit is often |1|.) +// +// SbBlitterContext objects keep track of draw state between a series of draw +// calls. Please refer to the SbBlitterContext() definition for more +// information about contexts. +// +// This function is thread-safe. +// +// This function returns kSbBlitterInvalidContext upon failure. Note that the +// function fails if it has already been used to create the maximum number +// of contexts. +// +// |device|: The SbBlitterDevice for which the SbBlitterContext object is +// created. SB_EXPORT SbBlitterContext SbBlitterCreateContext(SbBlitterDevice device); -// Destroys the specified |context| created by |device|, freeing all its -// resources. -// This function is not thread safe. -// Returns whether the destruction succeeded. +// Destroys the specified |context|, freeing all its resources. This function +// is not thread-safe. +// +// The return value indicates whether the destruction succeeded. +// +// |context|: The object to be destroyed. SB_EXPORT bool SbBlitterDestroyContext(SbBlitterContext context); -// Flushes all draw calls previously issued to |context|. After this call, -// all subsequent draw calls (on any context) are guaranteed to be processed -// by the device after all previous draw calls issued on this |context|. -// In many cases you will want to call this before calling -// SbBlitterFlipSwapChain(), to ensure that all draw calls are submitted before -// the flip occurs. -// This function is not thread safe. -// Returns whether the flush succeeded. +// Flushes all draw calls previously issued to |context|. Calling this function +// guarantees that the device processes all draw calls issued to this point on +// this |context| before processing any subsequent draw calls on any context. +// +// Before calling SbBlitterFlipSwapChain(), it is often prudent to call this +// function to ensure that all draw calls are submitted before the flip occurs. +// +// This function is not thread-safe. +// +// The return value indicates whether the flush succeeded. +// +// |context|: The context for which draw calls are being flushed. SB_EXPORT bool SbBlitterFlushContext(SbBlitterContext context); // Sets up |render_target| as the render target that all subsequent draw calls -// made on |context| will draw to. -// This function is not thread safe. -// Returns whether the render target was successfully set. +// made on |context| will use. +// +// This function is not thread-safe. +// +// The return value indicates whether the render target was set successfully. +// +// |context|: The object for which the render target is being set. +// |render_target|: The target that the |context| should use for draw calls. SB_EXPORT bool SbBlitterSetRenderTarget(SbBlitterContext context, SbBlitterRenderTarget render_target); -// Sets blending state on the specified |context|. If |blending| is true, the -// source alpha of subsequent draw calls will be used to blend with the -// destination color. In particular, Fc = Sc * Sa + Dc * (1 - Sa), where -// Fc is the final color, Sc is the source color, Sa is the source alpha, and -// Dc is the destination color. If |blending| is false, the source color and -// alpha will overwrite the destination color and alpha. By default blending +// Sets the blending state for the specified |context|. By default, blending // is disabled on a SbBlitterContext. -// This function is not thread safe. -// Returns whether the blending state was succcessfully set. +// +// This function is not thread-safe. +// +// The return value indicates whether the blending state was set successfully. +// +// |context|: The context for which the blending state is being set. +// |blending|: The blending state for the |context|. +// If |blending| is |true|, the source alpha of subsequent draw calls +// is used to blend with the destination color. In particular, +// (|Fc = Sc * Sa + Dc * (1 - Sa)|), where: +// - |Fc| is the final color. +// - |Sc| is the source color. +// - |Sa| is the source alpha. +// - |Dc| is the destination color. +// If |blending| is |false|, the source color and source alpha overwrite +// the destination color and alpha. SB_EXPORT bool SbBlitterSetBlending(SbBlitterContext context, bool blending); // Sets the context's current color. The current color's default value is -// SbBlitterColorFromRGBA(255, 255, 255 255). The current color affects the -// fill rectangle's color in calls to SbBlitterFillRect(), and if -// SbBlitterSetModulateBlitsWithColor() has been called to enable blit color -// modulation, the source blit surface pixel color will also be modulated by -// the color before being output. The color is specified in unpremultiplied -// alpha format. -// This function is not thread safe. -// Returns whether the color was successfully set. +// |SbBlitterColorFromRGBA(255, 255, 255 255)|. +// +// The current color affects the fill rectangle's color in calls to +// SbBlitterFillRect(). If SbBlitterSetModulateBlitsWithColor() has been called +// to enable blit color modulation, the source blit surface pixel color is also +// modulated by the color before being output. +// +// This function is not thread-safe. +// +// The return value indicates whether the color was set successfully. +// +// |context|: The context for which the color is being set. +// |color|: The context's new color, specified in unpremultiplied alpha format. SB_EXPORT bool SbBlitterSetColor(SbBlitterContext context, SbBlitterColor color); // Sets whether or not blit calls should have their source pixels modulated by -// the current color (set via a call to SbBlitterSetColor()) before being -// output. This can be used to apply opacity to blit calls, as well as for -// coloring alpha-only surfaces, and other effects. -// This function is not thread safe. -// Returns whether the state was successfully set. +// the current color, which is set using SbBlitterSetColor(), before being +// output. This function can apply opacity to blit calls, color alpha-only +// surfaces, and apply other effects. +// +// This function is not thread-safe. +// +// The return value indicates whether the state was set successfully. +// +// |modulate_blits_with_color|: Indicates whether to modulate source pixels +// in blit calls. SB_EXPORT bool SbBlitterSetModulateBlitsWithColor( SbBlitterContext context, bool modulate_blits_with_color); -// Sets the scissor rectangle. The scissor rectangle dictates a rectangle of -// visibility that affects all draw calls. Only pixels within the scissor -// rectangle will be rendered, all drawing outside of the scissor rectangle will -// be clipped. When SbBlitterSetRenderTarget() is called, the scissor rectangle -// is automatically set to the extents of the specified render target. If a +// Sets the scissor rectangle, which dictates a visibility area that affects +// all draw calls. Only pixels within the scissor rectangle are rendered, and +// all drawing outside of that area is clipped. +// +// When SbBlitterSetRenderTarget() is called, that function automatically sets +// the scissor rectangle to the size of the specified render target. If a // scissor rectangle is specified outside of the extents of the current render -// target bounds, it will be intersected with the render target boudns. It is -// an error to call this function before a render target has been specified for -// the context. -// This function is not thread safe. -// Returns whether the scissor was successfully set. +// target bounds, it will be intersected with the render target bounds. +// +// This function is not thread-safe. +// +// Returns whether the scissor was successfully set. It returns an error if +// it is called before a render target has been specified for the context. SB_EXPORT bool SbBlitterSetScissor(SbBlitterContext context, SbBlitterRect rect); -// Issues a draw call on |context| that fills a rectangle |rect|. The color -// of the rectangle is specified by the last call to SbBlitterSetColor(). -// This function is not thread safe. -// Returns whether the draw call succeeded. +// Issues a draw call on |context| that fills the specified rectangle |rect|. +// The rectangle's color is determined by the last call to SbBlitterSetColor(). +// +// This function is not thread-safe. +// +// The return value indicates whether the draw call succeeded. +// +// |context|: The context on which the draw call will operate. +// |rect|: The rectangle to be filled. SB_EXPORT bool SbBlitterFillRect(SbBlitterContext context, SbBlitterRect rect); -// Issues a draw call on |context| that blits an area of |source_surface| -// specified by a |src_rect| to |context|'s current render target at |dst_rect|. -// The source rectangle must lie within the dimensions of |source_surface|. The -// |source_surface|'s alpha will be modulated by |opacity| before being drawn. -// For |opacity|, a value of 0 implies complete invisibility and a value of -// 255 implies complete opaqueness. -// This function is not thread safe. -// Returns whether the draw call succeeded. +// Issues a draw call on |context| that blits the area of |source_surface| +// specified by |src_rect| to |context|'s current render target at |dst_rect|. +// The source rectangle must lie within the dimensions of |source_surface|. +// Note that the |source_surface|'s alpha is modulated by |opacity| before +// being drawn. For |opacity|, a value of 0 implies complete invisibility, +// and a value of 255 implies complete opacity. +// +// This function is not thread-safe. +// +// The return value indicates whether the draw call succeeded. +// +// |src_rect|: The area to be block transferred (blitted). SB_EXPORT bool SbBlitterBlitRectToRect(SbBlitterContext context, SbBlitterSurface source_surface, SbBlitterRect src_rect, SbBlitterRect dst_rect); -// This function does the exact same as SbBlitterBlitRectToRect(), except -// it permits values of |src_rect| outside of the dimensions of -// |source_surface| and in these regions the pixel data from |source_surface| -// will be wrapped. Negative values for |src_rect.x| and |src_rect.y| are -// allowed. The output will all be stretched to fit inside of |dst_rect|. -// This function is not thread safe. -// Returns whether the draw call succeeded. +// This function functions identically to SbBlitterBlitRectToRect(), except +// it permits values of |src_rect| outside the dimensions of |source_surface|. +// In those regions, the pixel data from |source_surface| will be wrapped. +// Negative values for |src_rect.x| and |src_rect.y| are allowed. +// +// The output is all stretched to fit inside of |dst_rect|. +// +// This function is not thread-safe. +// +// The return value indicates whether the draw call succeeded. SB_EXPORT bool SbBlitterBlitRectToRectTiled(SbBlitterContext context, SbBlitterSurface source_surface, SbBlitterRect src_rect, SbBlitterRect dst_rect); // This function achieves the same effect as calling SbBlitterBlitRectToRect() -// |num_rects| time with each of the |num_rects| values of |src_rects| and -// |dst_rects|. Using this function allows for greater efficiency than calling -// SbBlitterBlitRectToRect() in a loop. -// This function is not thread safe. -// Returns whether the draw call succeeded. +// |num_rects| times with each of the |num_rects| values of |src_rects| and +// |dst_rects|. This function allows for greater efficiency than looped calls +// to SbBlitterBlitRectToRect(). +// +// This function is not thread-safe. +// +// The return value indicates whether the draw call succeeded. SB_EXPORT bool SbBlitterBlitRectsToRects(SbBlitterContext context, SbBlitterSurface source_surface, const SbBlitterRect* src_rects,
diff --git a/src/starboard/build/convert_i18n_data.py b/src/starboard/build/convert_i18n_data.py index a34916a..0f56a59 100644 --- a/src/starboard/build/convert_i18n_data.py +++ b/src/starboard/build/convert_i18n_data.py
@@ -51,7 +51,7 @@ messages = root[0] # Write each message to the output file on its own line. - with open(output_filename, 'w') as output_file: + with open(output_filename, 'wb') as output_file: for msg in messages: # Use ; as the separator. Which means it better not be in the name. assert not (';' in msg.attrib['name'])
diff --git a/src/starboard/build/copy_data.py b/src/starboard/build/copy_data.py index cd1d6a2..c19c020 100644 --- a/src/starboard/build/copy_data.py +++ b/src/starboard/build/copy_data.py
@@ -14,7 +14,6 @@ # limitations under the License. # This file is based on build/copy_test_data_ios.py - """Copies data files or directories into a given output directory. Since the output of this script is intended to be use by GYP, all resulting @@ -29,6 +28,12 @@ if os.name == 'nt': import win32api +# The name of an environment variable that when set to |'1'|, signals to us +# that we should log all output directories that we have staged a copy to to +# our stderr output. This output could then, for example, be captured and +# analyzed by an external tool that is interested in these directories. +_ShouldLogEnvKey = 'STARBOARD_GYP_SHOULD_LOG_COPIES' + class WrongNumberOfArgumentsException(Exception): pass @@ -99,14 +104,27 @@ """Called by GYP using pymod_do_main.""" parser = argparse.ArgumentParser() parser.add_argument('-o', dest='output_dir', help='output directory') - parser.add_argument('--inputs', action='store_true', dest='list_inputs', - help='prints a list of all input files') - parser.add_argument('--outputs', action='store_true', dest='list_outputs', - help='prints a list of all output files') - parser.add_argument('input_paths', metavar='path', nargs='+', - help='path to an input file or directory') + parser.add_argument( + '--inputs', + action='store_true', + dest='list_inputs', + help='prints a list of all input files') + parser.add_argument( + '--outputs', + action='store_true', + dest='list_outputs', + help='prints a list of all output files') + parser.add_argument( + 'input_paths', + metavar='path', + nargs='+', + help='path to an input file or directory') options = parser.parse_args(argv) + if os.environ.get(_ShouldLogEnvKey, None) == '1': + if options.output_dir is not None: + print >> sys.stderr, 'COPY_LOG:', options.output_dir + escaped_files = [EscapePath(x) for x in options.input_paths] files_to_copy = CalcInputs(escaped_files) if options.list_inputs: @@ -133,5 +151,6 @@ print result return 0 + if __name__ == '__main__': sys.exit(main(sys.argv))
diff --git a/src/starboard/byte_swap.h b/src/starboard/byte_swap.h index 7c2e968..9fbd1fa 100644 --- a/src/starboard/byte_swap.h +++ b/src/starboard/byte_swap.h
@@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Functions for swapping byte order, used to deal with endianness when -// performing I/O +// Module Overview: Starboard Byte Swap module +// +// Specifies functions for swapping byte order. These functions are used to +// deal with endianness when performing I/O. #ifndef STARBOARD_BYTE_SWAP_H_ #define STARBOARD_BYTE_SWAP_H_ @@ -53,21 +55,27 @@ // and then act on that determination somehow. // Unconditionally swaps the byte order in signed 16-bit |value|. +// |value|: The value for which the byte order will be swapped. SB_EXPORT int16_t SbByteSwapS16(int16_t value); // Unconditionally swaps the byte order in unsigned 16-bit |value|. +// |value|: The value for which the byte order will be swapped. SB_EXPORT uint16_t SbByteSwapU16(uint16_t value); // Unconditionally swaps the byte order in signed 32-bit |value|. +// |value|: The value for which the byte order will be swapped. SB_EXPORT int32_t SbByteSwapS32(int32_t value); // Unconditionally swaps the byte order in unsigned 32-bit |value|. +// |value|: The value for which the byte order will be swapped. SB_EXPORT uint32_t SbByteSwapU32(uint32_t value); // Unconditionally swaps the byte order in signed 64-bit |value|. +// |value|: The value for which the byte order will be swapped. SB_EXPORT int64_t SbByteSwapS64(int64_t value); // Unconditionally swaps the byte order in unsigned 64-bit |value|. +// |value|: The value for which the byte order will be swapped. SB_EXPORT uint64_t SbByteSwapU64(uint64_t value); #ifdef __cplusplus
diff --git a/src/starboard/character.h b/src/starboard/character.h index f4d064f..f7758c7 100644 --- a/src/starboard/character.h +++ b/src/starboard/character.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Functions for interacting with characters. +// Module Overview: Starboard Character module +// +// Provides functions for interacting with characters. #ifndef STARBOARD_CHARACTER_H_ #define STARBOARD_CHARACTER_H_ @@ -26,36 +28,50 @@ #endif // Converts the given 8-bit character (as an int) to uppercase in the current -// locale, returning the result, also an 8-bit character. If there is no -// uppercase version of the character, or the character is already uppercase, it -// will just return the character as-is. -int SbCharacterToUpper(int c); +// locale and returns an 8-bit character. If there is no uppercase version of +// the character, or the character is already uppercase, the function just +// returns the character as-is. +// +// |c|: The character to be converted. +SB_EXPORT int SbCharacterToUpper(int c); // Converts the given 8-bit character (as an int) to lowercase in the current -// locale, returning the result, also an 8-bit character. If there is no -// lowercase version of the character, or the character is already lowercase, it -// will just return the character as-is. -int SbCharacterToLower(int c); +// locale and returns an 8-bit character. If there is no lowercase version of +// the character, or the character is already lowercase, the function just +// returns the character as-is. +// +// |c|: The character to be converted. +SB_EXPORT int SbCharacterToLower(int c); -// Returns whether the given 8-bit character |c| (as an int) is a space in the -// current locale. -bool SbCharacterIsSpace(int c); - -// Returns whether the given 8-bit character |c| (as an int) is uppercase in the -// current locale. -bool SbCharacterIsUpper(int c); - -// Returns whether the given 8-bit character |c| (as an int) is a decimal digit -// in the current locale. -bool SbCharacterIsDigit(int c); - -// Returns whether the given 8-bit character |c| (as an int) is a hexidecimal -// digit in the current locale. -bool SbCharacterIsHexDigit(int c); - -// Returns whether the given 8-bit character |c| (as an int) is alphanumeric in +// Indicates whether the given 8-bit character |c| (as an int) is a space in // the current locale. -bool SbCharacterIsAlphanumeric(int c); +// +// |c|: The character to be evaluated. +SB_EXPORT bool SbCharacterIsSpace(int c); + +// Indicates whether the given 8-bit character |c| (as an int) is uppercase +// in the current locale. +// +// |c|: The character to be evaluated. +SB_EXPORT bool SbCharacterIsUpper(int c); + +// Indicates whether the given 8-bit character |c| (as an int) is a +// decimal digit in the current locale. +// +// |c|: The character to be evaluated. +SB_EXPORT bool SbCharacterIsDigit(int c); + +// Indicates whether the given 8-bit character |c| (as an int) is a hexadecimal +// in the current locale. +// +// |c|: The character to be evaluated. +SB_EXPORT bool SbCharacterIsHexDigit(int c); + +// Indicates whether the given 8-bit character |c| (as an int) is alphanumeric +// in the current locale. +// +// |c|: The character to be evaluated. +SB_EXPORT bool SbCharacterIsAlphanumeric(int c); #ifdef __cplusplus } // extern "C"
diff --git a/src/starboard/client_porting/eztime/eztime.gyp b/src/starboard/client_porting/eztime/eztime.gyp index 009c29f..70f51e6 100644 --- a/src/starboard/client_porting/eztime/eztime.gyp +++ b/src/starboard/client_porting/eztime/eztime.gyp
@@ -32,9 +32,9 @@ 'target_name': 'eztime_test', 'type': '<(gtest_target_type)', 'sources': [ + '<(DEPTH)/starboard/common/test_main.cc', 'test_constants.h', 'eztime_test.cc', - '<(DEPTH)/starboard/nplb/main.cc', ], 'dependencies': [ '<(DEPTH)/testing/gmock.gyp:gmock',
diff --git a/src/starboard/client_porting/eztime/eztime.h b/src/starboard/client_porting/eztime/eztime.h index 8402006..8221c2f 100644 --- a/src/starboard/client_porting/eztime/eztime.h +++ b/src/starboard/client_porting/eztime/eztime.h
@@ -15,7 +15,9 @@ #ifndef STARBOARD_CLIENT_PORTING_EZTIME_EZTIME_H_ #define STARBOARD_CLIENT_PORTING_EZTIME_EZTIME_H_ +#include "starboard/log.h" #include "starboard/time.h" +#include "starboard/types.h" #ifdef __cplusplus extern "C" { @@ -119,7 +121,12 @@ // Converts SbTime to EzTimeValue. static SB_C_FORCE_INLINE EzTimeValue EzTimeValueFromSbTime(SbTime in_time) { EzTimeT sec = EzTimeTFromSbTime(in_time); - EzTimeValue value = {sec, in_time - EzTimeTToSbTime(sec)}; + SbTime diff = in_time - EzTimeTToSbTime(sec); + SB_DCHECK(diff >= INT_MIN); + SB_DCHECK(diff <= INT_MAX); + EzTimeValue value = {sec, (int)diff}; // Some compilers do not support + // returning the initializer list + // directly. return value; }
diff --git a/src/starboard/client_porting/poem/assert_poem.h b/src/starboard/client_porting/poem/assert_poem.h new file mode 100644 index 0000000..9d01a43 --- /dev/null +++ b/src/starboard/client_porting/poem/assert_poem.h
@@ -0,0 +1,40 @@ +// Copyright 2016 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. + +// A poem (POsix EMulation) for functions in string.h + +#ifndef STARBOARD_CLIENT_PORTING_POEM_ASSERT_POEM_H_ +#define STARBOARD_CLIENT_PORTING_POEM_ASSERT_POEM_H_ + +#if defined(STARBOARD) + +#include "starboard/log.h" + +#if !defined(POEM_NO_EMULATION) + +// On one line so that the assert macros do not interfere with reporting of line +// numbers in compiler error messages. +#define assert(x) \ + do { \ + if (!(x)) { \ + SbLogFormatF("expression %s failed at %s:%d", #x, __FILE__, __LINE__); \ + SbSystemBreakIntoDebugger(); \ + } \ + } while (false); + +#endif // POEM_NO_EMULATION + +#endif // STARBOARD + +#endif // STARBOARD_CLIENT_PORTING_POEM_ASSERT_POEM_H_
diff --git a/src/starboard/client_porting/poem/getenv_stub_poem.h b/src/starboard/client_porting/poem/getenv_stub_poem.h new file mode 100644 index 0000000..8e4ab81 --- /dev/null +++ b/src/starboard/client_porting/poem/getenv_stub_poem.h
@@ -0,0 +1,28 @@ +// Copyright 2016 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. + +// A poem (POsix EMulation) stub implementation for getenv. + +#ifndef STARBOARD_CLIENT_PORTING_POEM_GETENV_STUB_POEM_H_ +#define STARBOARD_CLIENT_PORTING_POEM_GETENV_STUB_POEM_H_ + +#include <stdlib.h> + +#include "starboard/configuration.h" + +static SB_C_INLINE const char* getenv(const char* unused) { + return NULL; +} + +#endif // STARBOARD_CLIENT_PORTING_POEM_GETENV_STUB_POEM_H_
diff --git a/src/starboard/client_porting/poem/inet_poem.h b/src/starboard/client_porting/poem/inet_poem.h new file mode 100644 index 0000000..55cf47a --- /dev/null +++ b/src/starboard/client_porting/poem/inet_poem.h
@@ -0,0 +1,27 @@ +// Copyright 2016 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. + +// A poem (POsix EMulation) for functions usually declared in <arpa/inet.h> + +#ifndef STARBOARD_CLIENT_PORTING_POEM_INET_POEM_H_ +#define STARBOARD_CLIENT_PORTING_POEM_INET_POEM_H_ + +#include "starboard/byte_swap.h" + +#define htonl(x) SB_HOST_TO_NET_U32(x) +#define htons(x) SB_HOST_TO_NET_U16(x) +#define ntohl(x) SB_NET_TO_HOST_U32(x) +#define ntohs(x) SB_NET_TO_HOST_U16(x) + +#endif // STARBOARD_CLIENT_PORTING_POEM_INET_POEM_H_
diff --git a/src/starboard/client_porting/poem/math_poem.h b/src/starboard/client_porting/poem/math_poem.h new file mode 100644 index 0000000..7f406d2 --- /dev/null +++ b/src/starboard/client_porting/poem/math_poem.h
@@ -0,0 +1,62 @@ +// Copyright 2016 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. + +// A poem (POsix EMulation) for functions in string.h + +#ifndef STARBOARD_CLIENT_PORTING_POEM_MATH_POEM_H_ +#define STARBOARD_CLIENT_PORTING_POEM_MATH_POEM_H_ + +#if defined(STARBOARD) + +#include "starboard/double.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Takes floor of a float |f|. Meant to be a drop-in replacement for |floorf| +static SB_C_INLINE float PoemSingleFloor(const float f) { + double d(f); + return SbDoubleFloor(d); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#if !defined(POEM_NO_EMULATION) + +#define fabs(x) SbDoubleAbsolute(x) +#define floor(x) SbDoubleFloor(x) +#define floorf(x) PoemSingleFloor(x) +#define pow(x, y) SbDoubleExponent(x, y) + +#include <math.h> +#define ceil(x) ceil(x) +#define fmod(x, y) fmod(x, y) +#define modf(x, y) modf(x, y) +#define log(x) log(x) +#define sqrt(x) sqrt(x) +#define sin(x) sin(x) +#define cos(x) cos(x) +#define tan(x) tan(x) +#define atan(x) atan(x) +#define atan2(x, y) atan2(x, y) +#define asin(x) asin(x) +#define acos(x) acos(x) +#endif // POEM_NO_EMULATION + +#endif // STARBOARD + +#endif // STARBOARD_CLIENT_PORTING_POEM_MATH_POEM_H_
diff --git a/src/starboard/client_porting/poem/poem.gyp b/src/starboard/client_porting/poem/poem.gyp index afde08e..947cc36 100644 --- a/src/starboard/client_porting/poem/poem.gyp +++ b/src/starboard/client_porting/poem/poem.gyp
@@ -19,10 +19,13 @@ 'type': 'static_library', 'sources': [ 'eztime_poem.h', + 'getenv_stub_poem.h', + 'inet_poem.h', 'stdio_poem.h', 'stdlib_poem.h', 'string_poem.h', 'strings_poem.h', + 'strnlen_poem.h', 'wchar_poem.h', ], 'dependencies': [ @@ -37,11 +40,23 @@ 'abs_tests.cc', 'include_all.c', 'main.cc', + 'string_poem_test.cc', ], 'dependencies': [ '<(DEPTH)/testing/gtest.gyp:gtest', '<(DEPTH)/starboard/starboard.gyp:starboard', ], }, + { + 'target_name': 'poem_unittests_deploy', + 'type': 'none', + 'dependencies': [ + 'poem_unittests', + ], + 'variables': { + 'executable_name': 'poem_unittests', + }, + 'includes': [ '../../build/deploy.gypi' ], + }, ], }
diff --git a/src/starboard/client_porting/poem/stdio_poem.h b/src/starboard/client_porting/poem/stdio_poem.h index 2500568..eab3f05 100644 --- a/src/starboard/client_porting/poem/stdio_poem.h +++ b/src/starboard/client_porting/poem/stdio_poem.h
@@ -21,8 +21,8 @@ #if !defined(POEM_NO_EMULATION) -#include "starboard/string.h" #include "starboard/memory.h" +#include "starboard/string.h" #define wcsncmp(s1, s2, c) SbStringCompareWide(s1, s2, c) @@ -34,9 +34,10 @@ #define sprintf SbStringFormatUnsafeF #define vsscanf SbStringScan #define sscanf SbStringScanF -#define malloc(sz) SbMemoryAllocateUnchecked(sz) -#define free(a) SbMemoryFree(a) -#define realloc(m, sz) SbMemoryReallocateUnchecked(m, sz) +#define malloc(sz) SbMemoryAllocate(sz) +#define calloc(c, s) SbMemoryCalloc(c, s) +#define free(a) SbMemoryDeallocate(a) +#define realloc(m, sz) SbMemoryReallocate(m, sz) #endif // POEM_NO_EMULATION
diff --git a/src/starboard/client_porting/poem/string_poem.h b/src/starboard/client_porting/poem/string_poem.h index 97ef46d..61ca795 100644 --- a/src/starboard/client_porting/poem/string_poem.h +++ b/src/starboard/client_porting/poem/string_poem.h
@@ -19,8 +19,9 @@ #if defined(STARBOARD) -#include "starboard/string.h" +#include "starboard/log.h" #include "starboard/memory.h" +#include "starboard/string.h" #ifdef __cplusplus @@ -31,7 +32,7 @@ // Finds the last occurrence of |character| in |str|, returning a pointer to // the found character in the given string, or NULL if not found. // Meant to be a drop-in replacement for strchr, C++ signature -inline char* PoemFindLastCharacter(char* str, int character) { +inline char* PoemFindLastCharacterInString(char* str, int character) { const char* const_str = static_cast<const char*>(str); const char c = static_cast<char>(character); return const_cast<char*>(SbStringFindLastCharacter(const_str, c)); @@ -40,7 +41,8 @@ // Finds the last occurrence of |character| in |str|, returning a pointer to // the found character in the given string, or NULL if not found. // Meant to be a drop-in replacement for strchr, C++ signature -inline const char* PoemFindLastCharacter(const char* str, int character) { +inline const char* PoemFindLastCharacterInString(const char* str, + int character) { const char c = static_cast<char>(character); return SbStringFindLastCharacter(str, c); } @@ -48,7 +50,7 @@ // Finds the first occurrence of |character| in |str|, returning a pointer to // the found character in the given string, or NULL if not found. // Meant to be a drop-in replacement for strchr -inline char* PoemFindCharacter(char* str, int character) { +inline char* PoemFindCharacterInString(char* str, int character) { const char* const_str = static_cast<const char*>(str); const char c = static_cast<char>(character); return const_cast<char*>(SbStringFindCharacter(const_str, c)); @@ -57,7 +59,7 @@ // Finds the first occurrence of |character| in |str|, returning a pointer to // the found character in the given string, or NULL if not found. // Meant to be a drop-in replacement for strchr -inline const char* PoemFindCharacter(const char* str, int character) { +inline const char* PoemFindCharacterInString(const char* str, int character) { const char c = static_cast<char>(character); return SbStringFindCharacter(str, c); } @@ -67,7 +69,8 @@ // Finds the first occurrence of |character| in |str|, returning a pointer to // the found character in the given string, or NULL if not found. // Meant to be a drop-in replacement for strchr -static SB_C_INLINE char* PoemFindCharacter(const char* str, int character) { +static SB_C_INLINE char* PoemFindCharacterInString(const char* str, + int character) { // C-style cast used for C code return (char*)(SbStringFindCharacter(str, character)); } @@ -75,7 +78,8 @@ // Finds the last occurrence of |character| in |str|, returning a pointer to // the found character in the given string, or NULL if not found. // Meant to be a drop-in replacement for strchr -static SB_C_INLINE char* PoemFindLastCharacter(const char* str, int character) { +static SB_C_INLINE char* PoemFindLastCharacterInString(const char* str, + int character) { // C-style cast used for C code return (char*)(SbStringFindLastCharacter(str, character)); } @@ -86,21 +90,60 @@ #endif // Concatenates |source| onto the end of |out_destination|, presuming it has -// |destination_size| total characters of storage available. Returns -// |out_destination|. This method is a drop-in replacement for strncat -static SB_C_INLINE char* PoemConcat(char* out_destination, - const char* source, - int destination_size) { - SbStringConcat(out_destination, source, destination_size); +// atleast strlen(out_destination) + |num_chars_to_copy| + 1 total characters of +// storage available. Returns |out_destination|. This method is a drop-in +// replacement for strncat. +// Note: even if num_chars_to_copy == 0, we will still write a NULL character. +// This is consistent with the language of linux's strncat man page: +// "Therefore, the size of dest must be at least strlen(dest)+n+1." +static SB_C_INLINE char* PoemStringConcat(char* out_destination, + const char* source, + int num_chars_to_copy) { + int destination_length = (int)(SbStringGetLength(out_destination)); + SbStringConcat(out_destination, source, + destination_length + num_chars_to_copy + 1); return out_destination; } -// Inline wrapper for an unsafe PoemConcat that assumes |out_destination| is -// big enough. Returns |out_destination|. Meant to be a drop-in replacement for -// strcat. -static SB_C_INLINE char* PoemConcatUnsafe(char* out_destination, - const char* source) { - return PoemConcat(out_destination, source, INT_MAX); +// Inline wrapper for an unsafe PoemStringConcat that assumes |out_destination| +// is big enough. Returns |out_destination|. Meant to be a drop-in replacement +// for strcat. +static SB_C_INLINE char* PoemStringConcatUnsafe(char* out_destination, + const char* source) { + return PoemStringConcat(out_destination, source, INT_MAX); +} + +// Inline wrapper for a drop-in replacement for |strncpy|. This function +// copies the null terminated string from src to dest. If the src string is +// shorter than num_chars_to_copy, then null padding is used. +// Warning: As with strncpy spec, if there is no null character within the first +// num_chars_to_copy in src, this function will write a null character at the +// end. +static SB_C_INLINE char* PoemStringCopyN(char* dest, + const char* src, + int num_chars_to_copy) { + SB_DCHECK(num_chars_to_copy >= 0); + if (num_chars_to_copy < 0) { + return dest; + } + + char* dest_write_iterator = dest; + char* dest_write_iterator_end = dest + num_chars_to_copy; + const char* src_iterator = src; + + while ((*src_iterator != '\0') && + (dest_write_iterator != dest_write_iterator_end)) { + *dest_write_iterator = *src_iterator; + + ++src_iterator; + ++dest_write_iterator; + } + + SB_DCHECK(dest_write_iterator_end >= dest_write_iterator); + SbMemorySet(dest_write_iterator, '\0', + dest_write_iterator_end - dest_write_iterator); + + return dest; } #ifdef __cplusplus @@ -111,12 +154,12 @@ #define strlen(s) SbStringGetLength(s) #define strcpy(o, s) SbStringCopyUnsafe(o, s) -#define strncpy(o, s, ds) SbStringCopy(o, s, ds) -#define strcat(o, s) PoemConcatUnsafe(o, s) -#define strncat(o, s, ds) PoemConcat(o, s, ds) +#define strncpy(o, s, ds) PoemStringCopyN(o, s, ds) +#define strcat(o, s) PoemStringConcatUnsafe(o, s) +#define strncat(o, s, ds) PoemStringConcat(o, s, ds) #define strdup(s) SbStringDuplicate(s) -#define strchr(s, c) PoemFindCharacter(s, c) -#define strrchr(s, c) PoemFindLastCharacter(s, c) +#define strchr(s, c) PoemFindCharacterInString(s, c) +#define strrchr(s, c) PoemFindLastCharacterInString(s, c) #define strstr(s, c) SbStringFindString(s, c) #define strncmp(s1, s2, c) SbStringCompare(s1, s2, c) #define strcmp(s1, s2) SbStringCompareAll(s1, s2)
diff --git a/src/starboard/client_porting/poem/string_poem_test.cc b/src/starboard/client_porting/poem/string_poem_test.cc new file mode 100644 index 0000000..1a3e899 --- /dev/null +++ b/src/starboard/client_porting/poem/string_poem_test.cc
@@ -0,0 +1,96 @@ +// Copyright 2016 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. + +// Test basic functionality of string poems. + +#include "testing/gtest/include/gtest/gtest.h" + +#include "starboard/client_porting/poem/string_poem.h" +#include "starboard/client_porting/poem/strnlen_poem.h" + +namespace starboard { +namespace nplb { +namespace { + +TEST(StringPoemTest, PoemStringConcat) { + char a[64] = "Hello "; + char b[] = "Yu"; + + strncat(a, b, sizeof(b) + 1); + + EXPECT_STREQ(a, "Hello Yu"); +} + +TEST(StringPoemTest, PoemStringCopy) { + char a[] = "Hello "; + char b[] = "Yu"; + + strncpy(a, b, sizeof(b)); + + EXPECT_STREQ(a, "Yu"); +} + +TEST(StringPoemTest, PoemStringCopyZeroBytes) { + char a[] = "Hello "; + char b[] = "Yu"; + + strncpy(a, b, 0); + + EXPECT_STREQ(a, "Hello "); +} + +TEST(StringPoemTest, PoemStringCopySrcNotNullTerminated) { + char a[] = "Hello "; + char b[] = "Yu"; + + strncpy(a, b, sizeof(b) - 1); + + EXPECT_STREQ(a, "Yullo "); +} + +TEST(StringPoemTest, PoemStringCopyNotEnoughSpace) { + char a[] = "Hello"; + char b[] = "Cobalt"; + + strncpy(a, b, sizeof(a) - 1); + + EXPECT_STREQ(a, "Cobal"); +} + +TEST(StringPoemTest, PoemStringCopySizeMoreThanNeeded) { + char a[] = "Hello"; + char b[] = "Wu"; + + strncpy(a, b, sizeof(a)); + + EXPECT_STREQ(a, "Wu"); +} + +TEST(StringPoemTest, PoemStringGetLengthFixed) { + char a[] = "abcdef"; + char b[] = "abc\0def"; + + EXPECT_EQ(strnlen(a, 0), 0); + EXPECT_EQ(strnlen(a, 3), 3); + EXPECT_EQ(strnlen(a, sizeof(a)), 6); + EXPECT_EQ(strnlen(a, 256), 6); + EXPECT_EQ(strnlen(b, 2), 2); + EXPECT_EQ(strnlen(b, 3), 3); + EXPECT_EQ(strnlen(b, sizeof(b)), 3); + EXPECT_EQ(strnlen(b, 256), 3); +} + +} // namespace +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/client_porting/poem/strnlen_poem.h b/src/starboard/client_porting/poem/strnlen_poem.h new file mode 100644 index 0000000..fb2d674 --- /dev/null +++ b/src/starboard/client_porting/poem/strnlen_poem.h
@@ -0,0 +1,33 @@ +// Copyright 2016 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. + +// A poem (POsix EMulation) implementation for strnlen. Usually declared in +// <string.h>, but may be missing on some platforms (e.g. PS3). + +#ifndef STARBOARD_CLIENT_PORTING_POEM_STRNLEN_POEM_H_ +#define STARBOARD_CLIENT_PORTING_POEM_STRNLEN_POEM_H_ + +#include "starboard/configuration.h" + +static SB_C_INLINE size_t StringGetLengthFixed(const char* s, size_t maxlen) { + size_t i = 0; + while (i < maxlen && s[i]) { + ++i; + } + return i; +} + +#define strnlen(s, maxlen) StringGetLengthFixed(s, maxlen) + +#endif // STARBOARD_CLIENT_PORTING_POEM_STRNLEN_POEM_H_
diff --git a/src/starboard/client_porting/pr_starboard/pr_starboard.cc b/src/starboard/client_porting/pr_starboard/pr_starboard.cc new file mode 100644 index 0000000..732ea14 --- /dev/null +++ b/src/starboard/client_porting/pr_starboard/pr_starboard.cc
@@ -0,0 +1,281 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/client_porting/pr_starboard/pr_starboard.h" + +#include "starboard/condition_variable.h" +#include "starboard/log.h" +#include "starboard/memory.h" +#include "starboard/mutex.h" +#include "starboard/once.h" +#include "starboard/queue.h" +#include "starboard/thread.h" +#include "starboard/time.h" +#include "starboard/types.h" + +namespace { + +typedef starboard::Queue<bool> SetupSignalQueue; + +// Utility function to convert a PRInterval to signed 64 bit integer +// microseconds. +int64_t PR_IntervalToMicrosecondsInt64(PRIntervalTime ticks) { + uint32_t microseconds_as_uint32 = PR_IntervalToMicroseconds(ticks); + int64_t microseconds_as_int64 = static_cast<int64_t>(microseconds_as_uint32); + return microseconds_as_int64; +} + +// Struct to bundle up arguments to be passed into SbThreadCreate. +struct ThreadEntryPointWrapperContext { + ThreadEntryPointWrapperContext(void* pr_context, + PRThread* pr_thread, + PRThreadEntryPoint pr_entry_point, + SetupSignalQueue* setup_signal_queue) + : pr_context(pr_context), + pr_thread(pr_thread), + pr_entry_point(pr_entry_point), + setup_signal_queue(setup_signal_queue) {} + void* pr_context; + PRThread* pr_thread; + PRThreadEntryPoint pr_entry_point; + SetupSignalQueue* setup_signal_queue; +}; + +// The thread local key that corresponds to where local PRThread* data is held. +SbThreadLocalKey g_pr_thread_local_key = kSbThreadLocalKeyInvalid; +// The SbOnceControlStructure to to ensure that the local key is only created +// once. +SbOnceControl g_pr_thread_key_once_control = SB_ONCE_INITIALIZER; + +void PrThreadDtor(void* value) { + PRThread* pr_thread = reinterpret_cast<PRThread*>(value); + delete pr_thread; +} + +void InitPrThreadKey() { + g_pr_thread_local_key = SbThreadCreateLocalKey(PrThreadDtor); +} + +SbThreadLocalKey GetPrThreadKey() { + SB_CHECK(SbOnce(&g_pr_thread_key_once_control, InitPrThreadKey)); + return g_pr_thread_local_key; +} + +void* ThreadEntryPointWrapper(void* context_as_void_pointer) { + ThreadEntryPointWrapperContext* context = + reinterpret_cast<ThreadEntryPointWrapperContext*>( + context_as_void_pointer); + void* pr_context = context->pr_context; + PRThreadEntryPoint pr_entry_point = context->pr_entry_point; + PRThread* pr_thread = context->pr_thread; + SetupSignalQueue* setup_signal_queue = context->setup_signal_queue; + + delete context; + + pr_thread->sb_thread = SbThreadGetCurrent(); + SbThreadLocalKey key = GetPrThreadKey(); + SB_CHECK(SbThreadIsValidLocalKey(key)); + SbThreadSetLocalValue(key, pr_thread); + + setup_signal_queue->Put(true); + pr_entry_point(pr_context); + + return NULL; +} + +} // namespace + +PRLock* PR_NewLock() { + PRLock* lock = new PRLock(); + if (!SbMutexCreate(lock)) { + delete lock; + return NULL; + } + return lock; +} + +void PR_DestroyLock(PRLock* lock) { + SB_DCHECK(lock); + SbMutexDestroy(lock); + delete lock; +} + +PRCondVar* PR_NewCondVar(PRLock* lock) { + SB_DCHECK(lock); + PRCondVar* cvar = new PRCondVar(); + if (!SbConditionVariableCreate(&cvar->sb_condition_variable, lock)) { + delete cvar; + return NULL; + } + cvar->lock = lock; + return cvar; +} + +void PR_DestroyCondVar(PRCondVar* cvar) { + SbConditionVariableDestroy(&cvar->sb_condition_variable); + delete cvar; +} + +PRStatus PR_WaitCondVar(PRCondVar* cvar, PRIntervalTime timeout) { + SbConditionVariableResult result; + if (timeout == PR_INTERVAL_NO_WAIT) { + result = SbConditionVariableWaitTimed(&cvar->sb_condition_variable, + cvar->lock, 0); + } else if (timeout == PR_INTERVAL_NO_TIMEOUT) { + result = SbConditionVariableWait(&cvar->sb_condition_variable, cvar->lock); + } else { + int64_t microseconds = PR_IntervalToMicrosecondsInt64(timeout); + result = SbConditionVariableWaitTimed(&cvar->sb_condition_variable, + cvar->lock, microseconds); + } + return pr_starboard::ToPRStatus(result != kSbConditionVariableFailed); +} + +PRThread* PR_GetCurrentThread() { + SbThreadLocalKey key = GetPrThreadKey(); + SB_CHECK(SbThreadIsValidLocalKey(key)); + + PRThread* value = static_cast<PRThread*>(SbThreadGetLocalValue(key)); + // We could potentially be a thread that was not created through + // PR_CreateThread. In this case, we must allocate a PRThread and do the + // setup that would normally have been done in PR_CreateThread. + if (!value) { + PRThread* pr_thread = new PRThread(SbThreadGetCurrent()); + SbThreadSetLocalValue(key, pr_thread); + value = pr_thread; + } + + return value; +} + +uint32_t PR_snprintf(char* out, uint32_t outlen, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + uint32_t ret = PR_vsnprintf(out, outlen, fmt, args); + va_end(args); + return ret; +} + +PRThread* PR_CreateThread(PRThreadType type, + PRThreadEntryPoint start, + void* arg, + PRThreadPriority priority, + PRThreadScope scope, + PRThreadState state, + PRUint32 stackSize) { + int64_t sb_stack_size = static_cast<int64_t>(stackSize); + + SbThreadPriority sb_priority; + switch (priority) { + case PR_PRIORITY_LOW: + sb_priority = kSbThreadPriorityLow; + break; + case PR_PRIORITY_NORMAL: + sb_priority = kSbThreadPriorityNormal; + break; + case PR_PRIORITY_HIGH: + sb_priority = kSbThreadPriorityHigh; + break; + case PR_PRIORITY_LAST: + sb_priority = kSbThreadPriorityHighest; + break; + default: + sb_priority = kSbThreadNoPriority; + } + + SbThreadAffinity sb_affinity = kSbThreadNoAffinity; + + SB_DCHECK(state == PR_JOINABLE_THREAD || state == PR_UNJOINABLE_THREAD); + bool sb_joinable = (state == PR_JOINABLE_THREAD); + + // This heap allocated PRThread object will have a pointer to it stored in + // the newly created child thread's thread local storage. Once the newly + // created child thread finishes, it will be freed in the destructor + // associated with it through thread local storage. + PRThread* pr_thread = new PRThread(kSbThreadInvalid); + + // Utility queue for the ThreadEntryWrapper to signal us once it's done + // running its initial setup and we can safely exit. + SetupSignalQueue setup_signal_queue; + + // This heap allocated context object is freed after + // ThreadEntryPointWrapper's initial setup is complete, right before the + // nspr level entry point is run. + ThreadEntryPointWrapperContext* context = new ThreadEntryPointWrapperContext( + arg, pr_thread, start, &setup_signal_queue); + + // Note that pr_thread->sb_thread will be set to the correct value in the + // setup section of ThreadEntryPointWrapper. It is done there rather than + // here to account for the unlikely but possible case in which we enter the + // newly created child thread, and then the child thread passes references + // to itself off into its potential children or co-threads that interact + // with it before we can copy what SbThreadCreate returns into + // pr_thread->sb_thread from this current thread. + SbThreadCreate(sb_stack_size, sb_priority, sb_affinity, sb_joinable, NULL, + ThreadEntryPointWrapper, context); + + // Now we must wait for the setup section of ThreadEntryPointWrapper to run + // and initialize pr_thread (both the struct itself and the corresponding + // new thread's private data) before we can safely return. We expect to + // receive true rather than false by convention. + bool setup_signal = setup_signal_queue.Get(); + SB_DCHECK(setup_signal); + + return pr_thread; +} + +PRStatus PR_CallOnceWithArg(PRCallOnceType* once, + PRCallOnceWithArgFN func, + void* arg) { + SB_NOTREACHED() << "Not implemented"; + return PR_FAILURE; +} + +PRStatus PR_NewThreadPrivateIndex(PRTLSIndex* newIndex, + PRThreadPrivateDTOR destructor) { + SbThreadLocalKey key = SbThreadCreateLocalKey(destructor); + if (!SbThreadIsValidLocalKey(key)) { + return pr_starboard::ToPRStatus(false); + } + *newIndex = key; + return pr_starboard::ToPRStatus(true); +} + +PRIntervalTime PR_MillisecondsToInterval(PRUint32 milli) { + PRUint64 tock = static_cast<PRUint64>(milli); + PRUint64 msecPerSec = static_cast<PRInt64>(PR_MSEC_PER_SEC); + PRUint64 rounding = static_cast<PRInt64>(PR_MSEC_PER_SEC >> 1); + PRUint64 tps = static_cast<PRInt64>(PR_TicksPerSecond()); + + tock *= tps; + tock += rounding; + tock /= msecPerSec; + + PRUint64 ticks = static_cast<PRUint64>(tock); + return ticks; +} + +PRUint32 PR_IntervalToMicroseconds(PRIntervalTime ticks) { + PRUint64 tock = static_cast<PRInt64>(ticks); + PRUint64 usecPerSec = static_cast<PRInt64>(PR_USEC_PER_SEC); + PRUint64 tps = static_cast<PRInt64>(PR_TicksPerSecond()); + PRUint64 rounding = static_cast<PRUint64>(tps) >> 1; + + tock *= usecPerSec; + tock += rounding; + tock /= tps; + + PRUint32 micro = static_cast<PRUint32>(tock); + return micro; +}
diff --git a/src/starboard/client_porting/pr_starboard/pr_starboard.gyp b/src/starboard/client_porting/pr_starboard/pr_starboard.gyp new file mode 100644 index 0000000..0874b65 --- /dev/null +++ b/src/starboard/client_porting/pr_starboard/pr_starboard.gyp
@@ -0,0 +1,29 @@ +# Copyright 2016 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. + +{ + 'targets': [ + { + 'target_name': 'pr_starboard', + 'type': 'static_library', + 'sources': [ + 'pr_starboard.cc', + 'pr_starboard.h', + ], + 'dependencies': [ + '<(DEPTH)/starboard/starboard.gyp:starboard', + ], + }, + ], +}
diff --git a/src/starboard/client_porting/pr_starboard/pr_starboard.h b/src/starboard/client_porting/pr_starboard/pr_starboard.h new file mode 100644 index 0000000..63e5c4d --- /dev/null +++ b/src/starboard/client_porting/pr_starboard/pr_starboard.h
@@ -0,0 +1,189 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This header defines the interface for a starboard based implementation of +// the subset of nspr that SpiderMonkey depends on. It directly matches the +// NSPR API, with the exception that accessing thread local data should use +// PRTLSIndex, rather than PRUintn. + +#ifndef STARBOARD_CLIENT_PORTING_PR_STARBOARD_PR_STARBOARD_H_ +#define STARBOARD_CLIENT_PORTING_PR_STARBOARD_PR_STARBOARD_H_ + +#include "starboard/condition_variable.h" +#include "starboard/log.h" +#include "starboard/memory.h" +#include "starboard/mutex.h" +#include "starboard/string.h" +#include "starboard/thread.h" +#include "starboard/types.h" + +#define PR_CALLBACK + +#define PR_MSEC_PER_SEC 1000L +#define PR_USEC_PER_SEC 1000000L +#define PR_NSEC_PER_SEC 1000000000L +#define PR_USEC_PER_MSEC 1000L +#define PR_NSEC_PER_MSEC 1000000L + +typedef enum { PR_FAILURE = -1, PR_SUCCESS = 0 } PRStatus; + +namespace pr_starboard { + +// Utility function to map true to PR_SUCCESS and false to PR_FAILURE. +static inline PRStatus ToPRStatus(bool result) { + return result ? PR_SUCCESS : PR_FAILURE; +} + +} // namespace pr_starboard + +typedef enum PRThreadPriority { + PR_PRIORITY_FIRST = 0, + PR_PRIORITY_LOW = 0, + PR_PRIORITY_NORMAL = 1, + PR_PRIORITY_HIGH = 2, + PR_PRIORITY_URGENT = 3, + PR_PRIORITY_LAST = 3 +} PRThreadPriority; + +typedef enum PRThreadScope { + PR_LOCAL_THREAD, + PR_GLOBAL_THREAD, + PR_GLOBAL_BOUND_THREAD +} PRThreadScope; + +typedef enum PRThreadState { + PR_JOINABLE_THREAD, + PR_UNJOINABLE_THREAD +} PRThreadState; + +typedef enum PRThreadType { PR_USER_THREAD, PR_SYSTEM_THREAD } PRThreadType; + +typedef SbThreadLocalKey PRTLSIndex; +typedef uint32_t PRIntervalTime; + +typedef int32_t PRInt32; +typedef uint32_t PRUint32; + +typedef int64_t PRInt64; +typedef uint64_t PRUint64; + +typedef void(PR_CALLBACK* PRThreadPrivateDTOR)(void* priv); + +struct PRThread { + explicit PRThread(SbThread sb_thread) : sb_thread(sb_thread) {} + SbThread sb_thread; +}; + +typedef SbMutex PRLock; + +#define PR_INTERVAL_NO_WAIT 0UL +#define PR_INTERVAL_NO_TIMEOUT 0xffffffffUL + +struct PRCondVar { + SbConditionVariable sb_condition_variable; + SbMutex* lock; +}; + +PRLock* PR_NewLock(); + +static inline void PR_Lock(PRLock* lock) { + SbMutexAcquire(lock); +} + +static inline void PR_Unlock(PRLock* lock) { + SbMutexRelease(lock); +} + +void PR_DestroyLock(PRLock* lock); + +PRCondVar* PR_NewCondVar(PRLock* lock); + +void PR_DestroyCondVar(PRCondVar* cvar); + +PRStatus PR_WaitCondVar(PRCondVar* cvar, PRIntervalTime timeout); + +static inline PRStatus PR_NotifyCondVar(PRCondVar* cvar) { + return pr_starboard::ToPRStatus( + SbConditionVariableSignal(&cvar->sb_condition_variable)); +} + +static inline PRStatus PR_NotifyAllCondVar(PRCondVar* cvar) { + return pr_starboard::ToPRStatus( + SbConditionVariableBroadcast(&cvar->sb_condition_variable)); +} + +typedef void (*PRThreadEntryPoint)(void*); + +PRThread* PR_CreateThread(PRThreadType type, + PRThreadEntryPoint start, + void* arg, + PRThreadPriority priority, + PRThreadScope scope, + PRThreadState state, + PRUint32 stackSize); + +static inline PRStatus PR_JoinThread(PRThread* pr_thread) { + SB_DCHECK(pr_thread); + SB_DCHECK(SbThreadIsValid(pr_thread->sb_thread)); + return pr_starboard::ToPRStatus(SbThreadJoin(pr_thread->sb_thread, NULL)); +} + +PRThread* PR_GetCurrentThread(); + +void PR_DetachThread(); + +PRStatus PR_NewThreadPrivateIndex(PRTLSIndex* newIndex, + PRThreadPrivateDTOR destructor); + +static inline PRStatus PR_SetThreadPrivate(PRTLSIndex index, void* priv) { + return pr_starboard::ToPRStatus(SbThreadSetLocalValue(index, priv)); +} + +static inline void* PR_GetThreadPrivate(PRTLSIndex index) { + return SbThreadGetLocalValue(index); +} + +static inline void PR_SetCurrentThreadName(const char* name) { + SbThreadSetName(name); +} + +static inline PRUint32 PR_vsnprintf(char* out, + PRUint32 outlen, + const char* fmt, + va_list ap) { + return static_cast<PRUint32>(SbStringFormat(out, outlen, fmt, ap)); +} + +PRUint32 PR_snprintf(char* out, PRUint32 outlen, const char* fmt, ...); + +PRIntervalTime PR_MillisecondsToInterval(PRUint32 milli); + +static inline PRIntervalTime PR_MicrosecondsToInterval(PRUint32 micro) { + return (micro + 999) / 1000; +} + +PRUint32 PR_IntervalToMicroseconds(PRIntervalTime ticks); + +struct PRCallOnceType {}; +typedef PRStatus(PR_CALLBACK* PRCallOnceWithArgFN)(void* arg); + +PRStatus PR_CallOnceWithArg(PRCallOnceType* once, + PRCallOnceWithArgFN func, + void* arg); + +static inline PRUint32 PR_TicksPerSecond() { + return 1000; +} + +#endif // STARBOARD_CLIENT_PORTING_PR_STARBOARD_PR_STARBOARD_H_
diff --git a/src/starboard/client_porting/wrap_main/wrap_main.h b/src/starboard/client_porting/wrap_main/wrap_main.h index e4879d3..4734772 100644 --- a/src/starboard/client_porting/wrap_main/wrap_main.h +++ b/src/starboard/client_porting/wrap_main/wrap_main.h
@@ -50,6 +50,11 @@ } // namespace client_porting } // namespace starboard +#if defined(_WIN32) +// Today there is no Starboard win32. Make sure those who create it know +// the _CrtSet* functions below should be moved to starboard win32 main. +#error For starboard win32, please move _CrtSet* to main +#endif #define STARBOARD_WRAP_SIMPLE_MAIN(main_function) \ void SbEventHandle(const SbEvent* event) { \ ::starboard::client_porting::wrap_main::SimpleEventHandler< \ @@ -57,10 +62,26 @@ } #else +#if defined(_WIN32) +#include <windows.h> + +// TODO this case should be removed when win32 is starboardized +#define STARBOARD_WRAP_SIMPLE_MAIN(main_function) \ + int main(int argc, char** argv) { \ + if (!IsDebuggerPresent()) { \ + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); \ + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); \ + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); \ + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); \ + } \ + return main_function(argc, argv); \ + } +#else // defined(_WIN32) #define STARBOARD_WRAP_SIMPLE_MAIN(main_function) \ int main(int argc, char** argv) { \ return main_function(argc, argv); \ } +#endif #endif // STARBOARD #endif // STARBOARD_CLIENT_PORTING_WRAP_MAIN_WRAP_MAIN_H_
diff --git a/src/starboard/common/common.cc b/src/starboard/common/common.cc new file mode 100644 index 0000000..86c63c2 --- /dev/null +++ b/src/starboard/common/common.cc
@@ -0,0 +1,21 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/configuration.h" + +// This audit is here so it is only displayed once every build. +#if SB_API_VERSION == SB_EXPERIMENTAL_API_VERSION +#pragma message "Your platform's SB_API_VERSION == SB_EXPERIMENTAL_API_VERSION." +#pragma message "You are implementing the experimental SB API at your own risk!" +#endif
diff --git a/src/starboard/common/common.gyp b/src/starboard/common/common.gyp new file mode 100644 index 0000000..c6a509b --- /dev/null +++ b/src/starboard/common/common.gyp
@@ -0,0 +1,45 @@ +# Copyright 2016 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The "common" target contains facilities provided by Starboard that are common +# to all platforms. + +{ + 'targets': [ + { + 'target_name': 'common', + 'type': 'static_library', + 'variables': { + 'includes_starboard': 1, + }, + 'sources': [ + 'common.cc', + 'decode_target_provider.cc', + 'memory.cc', + 'move.h', + 'ref_counted.cc', + 'ref_counted.h', + 'reset_and_return.h', + 'scoped_ptr.h', + 'thread_collision_warner.cc', + 'thread_collision_warner.h', + ], + 'defines': [ + # This must be defined when building Starboard, and must not when + # building Starboard client code. + 'STARBOARD_IMPLEMENTATION', + ], + }, + ], +}
diff --git a/src/starboard/common/decode_target_provider.cc b/src/starboard/common/decode_target_provider.cc new file mode 100644 index 0000000..673d091 --- /dev/null +++ b/src/starboard/common/decode_target_provider.cc
@@ -0,0 +1,97 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/decode_target.h" +#include "starboard/log.h" +#include "starboard/mutex.h" + +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + +namespace { +struct Registry { + SbMutex mutex; + SbDecodeTargetProvider* provider; + void* context; +}; + +Registry g_registry = {SB_MUTEX_INITIALIZER}; +} // namespace + +bool SbDecodeTargetRegisterProvider(SbDecodeTargetProvider* provider, + void* context) { + SbMutexAcquire(&(g_registry.mutex)); + if (g_registry.provider || g_registry.context) { + SB_DLOG(WARNING) << __FUNCTION__ << ": " + << "Registering (P" << provider << ", C" << context + << ") while " + << "(P" << g_registry.provider << ", C" + << g_registry.context << ") is already registered."; + } + g_registry.provider = provider; + g_registry.context = context; + SbMutexRelease(&(g_registry.mutex)); + return true; +} + +void SbDecodeTargetUnregisterProvider(SbDecodeTargetProvider* provider, + void* context) { + SbMutexAcquire(&(g_registry.mutex)); + if (g_registry.provider == provider && g_registry.context == context) { + g_registry.provider = NULL; + g_registry.context = NULL; + } else { + SB_DLOG(WARNING) << __FUNCTION__ << ": " + << "Unregistering (P" << provider << ", C" << context + << ") while " + << "(P" << g_registry.provider << ", C" + << g_registry.context << ") is what is registered."; + } + SbMutexRelease(&(g_registry.mutex)); +} + +SbDecodeTarget SbDecodeTargetAcquireFromProvider(SbDecodeTargetFormat format, + int width, + int height) { + SbDecodeTargetProvider* provider = NULL; + void* context = NULL; + SbMutexAcquire(&(g_registry.mutex)); + provider = g_registry.provider; + context = g_registry.context; + SbMutexRelease(&(g_registry.mutex)); + if (!provider) { + SB_DLOG(ERROR) << __FUNCTION__ << ": " + << "No registered provider."; + return kSbDecodeTargetInvalid; + } + + return provider->acquire(context, format, width, height); +} + +void SbDecodeTargetReleaseToProvider(SbDecodeTarget decode_target) { + SbDecodeTargetProvider* provider = NULL; + void* context = NULL; + SbMutexAcquire(&(g_registry.mutex)); + provider = g_registry.provider; + context = g_registry.context; + SbMutexRelease(&(g_registry.mutex)); + if (!provider) { + SB_DLOG(ERROR) << __FUNCTION__ << ": " + << "No registered provider."; + return; + } + + provider->release(context, decode_target); +} + +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS)
diff --git a/src/starboard/common/memory.cc b/src/starboard/common/memory.cc new file mode 100644 index 0000000..4ed3362 --- /dev/null +++ b/src/starboard/common/memory.cc
@@ -0,0 +1,222 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/atomic.h" +#include "starboard/log.h" +#include "starboard/memory.h" +#include "starboard/memory_reporter.h" +#include "starboard/shared/starboard/memory_reporter_internal.h" + +namespace { +inline void* SbMemoryAllocateImpl(size_t size); +inline void* SbMemoryAllocateAlignedImpl(size_t alignment, size_t size); +inline void* SbMemoryReallocateImpl(void* memory, size_t size); +inline void SbReportAllocation(const void* memory, size_t size); +inline void SbReportDeallocation(const void* memory); + +SbMemoryReporter* s_memory_reporter = NULL; + +bool LeakTraceEnabled(); // True when leak tracing enabled. +bool StarboardAllowsMemoryTracking(); // True when build enabled. +} // namespace. + +bool SbMemorySetReporter(SbMemoryReporter* reporter) { + // TODO: We should run a runtime test here with a test memory + // reporter that determines whether global operator new/delete are properly + // overridden. This problem appeared with address sanitizer and given + // how tricky operator new/delete are in general (see a google search) + // it's reasonable to assume that the likely hood of this happening again + // is high. To allow the QA/developer to take corrective action, this + // condition needs to be detected at runtime with a simple error message so + // that corrective action (i.e. running a different build) can be applied. + // + // RunOperatorNewDeleteRuntimeTest(); // Implement me. + + // Flush local memory to main so that other threads don't + // see a partially constructed reporter due to memory + // re-ordering. + SbAtomicMemoryBarrier(); + s_memory_reporter = reporter; + + // These are straight forward error messages. We use the build settings to + // predict whether the MemoryReporter is likely to fail. + if (!StarboardAllowsMemoryTracking()) { + SbLogRaw("\nMemory Reporting is disabled because this build does " + "not support it. Try a QA, devel or debug build.\n"); + return false; + } else if (LeakTraceEnabled()) { + SbLogRaw("\nMemory Reporting might be disabled because leak trace " + "(from address sanitizer?) is active.\n"); + return false; + } + return true; +} + +void* SbMemoryAllocate(size_t size) { + void* memory = SbMemoryAllocateImpl(size); + SbReportAllocation(memory, size); + return memory; +} + +void* SbMemoryAllocateAligned(size_t alignment, size_t size) { + void* memory = SbMemoryAllocateAlignedImpl(alignment, size); + SbReportAllocation(memory, size); + return memory; +} + +void* SbMemoryReallocate(void* memory, size_t size) { + SbReportDeallocation(memory); + void* new_memory = SbMemoryReallocateImpl(memory, size); + SbReportAllocation(new_memory, size); + return new_memory; +} + +void SbMemoryDeallocate(void* memory) { + // Report must happen first or else a race condition allows the memory to + // be freed and then reported as allocated, before the allocation is removed. + SbReportDeallocation(memory); + SbMemoryFree(memory); +} + +void SbMemoryDeallocateAligned(void* memory) { + // Report must happen first or else a race condition allows the memory to + // be freed and then reported as allocated, before the allocation is removed. + SbReportDeallocation(memory); + SbMemoryFreeAligned(memory); +} + +// Same as SbMemoryReallocateUnchecked, but will abort() in the case of an +// allocation failure +void* SbMemoryReallocateChecked(void* memory, size_t size) { + void* address = SbMemoryReallocateUnchecked(memory, size); + SbAbortIfAllocationFailed(size, address); + return address; +} + +// Same as SbMemoryAllocateAlignedUnchecked, but will abort() in the case of an +// allocation failure +void* SbMemoryAllocateAlignedChecked(size_t alignment, size_t size) { + void* address = SbMemoryAllocateAlignedUnchecked(alignment, size); + SbAbortIfAllocationFailed(size, address); + return address; +} + +void* SbMemoryAllocateChecked(size_t size) { + void* address = SbMemoryAllocateUnchecked(size); + SbAbortIfAllocationFailed(size, address); + return address; +} + +void SbMemoryReporterReportMappedMemory(const void* memory, size_t size) { +#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + return; +#else + if (SB_LIKELY(!s_memory_reporter)) { + return; + } + s_memory_reporter->on_mapmem_cb( + s_memory_reporter->context, + memory, + size); +#endif // STARBOARD_ALLOWS_MEMORY_TRACKING +} + +void SbMemoryReporterReportUnmappedMemory(const void* memory, size_t size) { +#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + return; +#else + if (SB_LIKELY(!s_memory_reporter)) { + return; + } + s_memory_reporter->on_unmapmem_cb( + s_memory_reporter->context, + memory, + size); +#endif // STARBOARD_ALLOWS_MEMORY_TRACKING +} + +namespace { // anonymous namespace. + +inline void SbReportAllocation(const void* memory, size_t size) { +#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + return; +#else + if (SB_LIKELY(!s_memory_reporter)) { + return; + } + s_memory_reporter->on_alloc_cb( + s_memory_reporter->context, + memory, + size); +#endif // STARBOARD_ALLOWS_MEMORY_TRACKING +} + +inline void SbReportDeallocation(const void* memory) { +#if !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + return; +#else + if (SB_LIKELY(!s_memory_reporter)) { + return; + } + s_memory_reporter->on_dealloc_cb( + s_memory_reporter->context, + memory); +#endif // STARBOARD_ALLOWS_MEMORY_TRACKING +} + +inline void* SbMemoryAllocateImpl(size_t size) { +#if SB_ABORT_ON_ALLOCATION_FAILURE + return SbMemoryAllocateChecked(size); +#else + return SbMemoryAllocateUnchecked(size); +#endif +} + +inline void* SbMemoryAllocateAlignedImpl(size_t alignment, size_t size) { +#if SB_ABORT_ON_ALLOCATION_FAILURE + return SbMemoryAllocateAlignedChecked(alignment, size); +#else + return SbMemoryAllocateAlignedUnchecked(alignment, size); +#endif +} + +inline void* SbMemoryReallocateImpl(void* memory, size_t size) { +#if SB_ABORT_ON_ALLOCATION_FAILURE + return SbMemoryReallocateChecked(memory, size); +#else + return SbMemoryReallocateUnchecked(memory, size); +#endif +} + +bool LeakTraceEnabled() { +#if defined(HAS_LEAK_SANITIZER) && (0 == HAS_LEAK_SANITIZER) + // In this build the leak tracer is specifically disabled. This + // build condition is typical for some builds of address sanitizer. + return true; +#elif defined(ADDRESS_SANITIZER) + // Leak tracer is not specifically disabled and address sanitizer is running. + return true; +#else + return false; +#endif +} + +bool StarboardAllowsMemoryTracking() { +#if defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + return true; +#else + return false; +#endif +} +} // namespace
diff --git a/src/starboard/common/move.h b/src/starboard/common/move.h new file mode 100644 index 0000000..aacd8e6 --- /dev/null +++ b/src/starboard/common/move.h
@@ -0,0 +1,209 @@ +// 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. + +#ifndef STARBOARD_COMMON_MOVE_H_ +#define STARBOARD_COMMON_MOVE_H_ + +// Macro with the boilerplate that makes a type move-only in C++03. +// +// USAGE +// +// This macro should be used instead of DISALLOW_COPY_AND_ASSIGN to create +// a "move-only" type. Unlike DISALLOW_COPY_AND_ASSIGN, this macro should be +// the first line in a class declaration. +// +// A class using this macro must call .Pass() (or somehow be an r-value already) +// before it can be: +// +// * Passed as a function argument +// * Used as the right-hand side of an assignment +// * Returned from a function +// +// Each class will still need to define their own "move constructor" and "move +// operator=" to make this useful. Here's an example of the macro, the move +// constructor, and the move operator= from the scoped_ptr class: +// +// template <typename T> +// class scoped_ptr { +// MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) +// public: +// scoped_ptr(RValue& other) : ptr_(other.release()) { } +// scoped_ptr& operator=(RValue& other) { +// swap(other); +// return *this; +// } +// }; +// +// Note that the constructor must NOT be marked explicit. +// +// For consistency, the second parameter to the macro should always be RValue +// unless you have a strong reason to do otherwise. It is only exposed as a +// macro parameter so that the move constructor and move operator= don't look +// like they're using a phantom type. +// +// +// HOW THIS WORKS +// +// For a thorough explanation of this technique, see: +// +// http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Move_Constructor +// +// The summary is that we take advantage of 2 properties: +// +// 1) non-const references will not bind to r-values. +// 2) C++ can apply one user-defined conversion when initializing a +// variable. +// +// The first lets us disable the copy constructor and assignment operator +// by declaring private version of them with a non-const reference parameter. +// +// For l-values, direct initialization still fails like in +// DISALLOW_COPY_AND_ASSIGN because the copy constructor and assignment +// operators are private. +// +// For r-values, the situation is different. The copy constructor and +// assignment operator are not viable due to (1), so we are trying to call +// a non-existent constructor and non-existing operator= rather than a private +// one. Since we have not committed an error quite yet, we can provide an +// alternate conversion sequence and a constructor. We add +// +// * a private struct named "RValue" +// * a user-defined conversion "operator RValue()" +// * a "move constructor" and "move operator=" that take the RValue& as +// their sole parameter. +// +// Only r-values will trigger this sequence and execute our "move constructor" +// or "move operator=." L-values will match the private copy constructor and +// operator= first giving a "private in this context" error. This combination +// gives us a move-only type. +// +// For signaling a destructive transfer of data from an l-value, we provide a +// method named Pass() which creates an r-value for the current instance +// triggering the move constructor or move operator=. +// +// Other ways to get r-values is to use the result of an expression like a +// function call. +// +// Here's an example with comments explaining what gets triggered where: +// +// class Foo { +// MOVE_ONLY_TYPE_FOR_CPP_03(Foo, RValue); +// +// public: +// ... API ... +// Foo(RValue other); // Move constructor. +// Foo& operator=(RValue rhs); // Move operator= +// }; +// +// Foo MakeFoo(); // Function that returns a Foo. +// +// Foo f; +// Foo f_copy(f); // ERROR: Foo(Foo&) is private in this context. +// Foo f_assign; +// f_assign = f; // ERROR: operator=(Foo&) is private in this context. +// +// +// Foo f(MakeFoo()); // R-value so alternate conversion executed. +// Foo f_copy(f.Pass()); // R-value so alternate conversion executed. +// f = f_copy.Pass(); // R-value so alternate conversion executed. +// +// +// IMPLEMENTATION SUBTLETIES WITH RValue +// +// The RValue struct is just a container for a pointer back to the original +// object. It should only ever be created as a temporary, and no external +// class should ever declare it or use it in a parameter. +// +// It is tempting to want to use the RValue type in function parameters, but +// excluding the limited usage here for the move constructor and move +// operator=, doing so would mean that the function could take both r-values +// and l-values equially which is unexpected. See COMPARED To Boost.Move for +// more details. +// +// An alternate, and incorrect, implementation of the RValue class used by +// Boost.Move makes RValue a fieldless child of the move-only type. RValue& +// is then used in place of RValue in the various operators. The RValue& is +// "created" by doing *reinterpret_cast<RValue*>(this). This has the appeal +// of never creating a temproary RValue struct even with optimizations +// disabled. Also, by virtue of inheritance you can treat the RValue +// reference as if it were the move-only type itself. Unfortuantely, +// using the result of this reinterpret_cast<> is actually undefined behavior +// due to C++98 5.2.10.7. In certain compilers (eg., NaCl) the optimizer +// will generate non-working code. +// +// In optimized builds, both implementations generate the same assembly so we +// choose the one that adheres to the standard. +// +// +// COMPARED TO C++11 +// +// In C++11, you would implement this functionality using an r-value reference +// and our .Pass() method would be replaced with a call to std::move(). +// +// This emulation also has a deficiency where it uses up the single +// user-defined conversion allowed by C++ during initialization. This can +// cause problems in some API edge cases. For instance, in scoped_ptr, it is +// impossible to make an function "void Foo(scoped_ptr<Parent> p)" accept a +// value of type scoped_ptr<Child> even if you add a constructor to +// scoped_ptr<> that would make it look like it should work. C++11 does not +// have this deficiency. +// +// +// COMPARED TO Boost.Move +// +// Our implementation similar to Boost.Move, but we keep the RValue struct +// private to the move-only type, and we don't use the reinterpret_cast<> hack. +// +// In Boost.Move, RValue is the boost::rv<> template. This type can be used +// when writing APIs like: +// +// void MyFunc(boost::rv<Foo>& f) +// +// that can take advantage of rv<> to avoid extra copies of a type. However you +// would still be able to call this version of MyFunc with an l-value: +// +// Foo f; +// MyFunc(f); // Uh oh, we probably just destroyed |f| w/o calling Pass(). +// +// unless someone is very careful to also declare a parallel override like: +// +// void MyFunc(const Foo& f) +// +// that would catch the l-values first. This was declared unsafe in C++11 and +// a C++11 compiler will explicitly fail MyFunc(f). Unfortunately, we cannot +// ensure this in C++03. +// +// Since we have no need for writing such APIs yet, our implementation keeps +// RValue private and uses a .Pass() method to do the conversion instead of +// trying to write a version of "std::move()." Writing an API like std::move() +// would require the RValue structs to be public. +// +// +// CAVEATS +// +// If you include a move-only type as a field inside a class that does not +// explicitly declare a copy constructor, the containing class's implicit +// copy constructor will change from Containing(const Containing&) to +// Containing(Containing&). This can cause some unexpected errors. +// +// http://llvm.org/bugs/show_bug.cgi?id=11528 +// +// The workaround is to explicitly declare your copy constructor. +// +#define MOVE_ONLY_TYPE_FOR_CPP_03(type, rvalue_type) \ + private: \ + struct rvalue_type { \ + rvalue_type(type* object) : object(object) {} \ + type* object; \ + }; \ + type(type&); \ + void operator=(type&); \ + \ + public: \ + operator rvalue_type() { return rvalue_type(this); } \ + type Pass() { return type(rvalue_type(this)); } \ + \ + private: + +#endif // STARBOARD_COMMON_MOVE_H_
diff --git a/src/starboard/common/ref_counted.cc b/src/starboard/common/ref_counted.cc new file mode 100644 index 0000000..32f67d5 --- /dev/null +++ b/src/starboard/common/ref_counted.cc
@@ -0,0 +1,95 @@ +// Copyright (c) 2011 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 "starboard/common/ref_counted.h" + +#include "starboard/log.h" + +namespace starboard { + +namespace subtle { + +RefCountedBase::RefCountedBase() + : ref_count_(0) +#ifndef NDEBUG + , + in_dtor_(false) +#endif +{ +} + +RefCountedBase::~RefCountedBase() { +#ifndef NDEBUG + SB_DCHECK(in_dtor_) << "RefCounted object deleted without calling Release()"; +#endif +} + +void RefCountedBase::AddRef() const { +// TODO(maruel): Add back once it doesn't assert 500 times/sec. +// Current thread books the critical section "AddRelease" without release it. +// DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_); +#ifndef NDEBUG + SB_DCHECK(!in_dtor_); +#endif + ++ref_count_; +} + +bool RefCountedBase::Release() const { +// TODO(maruel): Add back once it doesn't assert 500 times/sec. +// Current thread books the critical section "AddRelease" without release it. +// DFAKE_SCOPED_LOCK_THREAD_LOCKED(add_release_); +#ifndef NDEBUG + SB_DCHECK(!in_dtor_); +#endif + if (--ref_count_ == 0) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + return true; + } + return false; +} + +bool RefCountedThreadSafeBase::HasOneRef() const { + return (SbAtomicAcquire_Load( + &const_cast<RefCountedThreadSafeBase*>(this)->ref_count_) == 1); +} + +RefCountedThreadSafeBase::RefCountedThreadSafeBase() : ref_count_(0) { +#ifndef NDEBUG + in_dtor_ = false; +#endif +} + +RefCountedThreadSafeBase::~RefCountedThreadSafeBase() { +#ifndef NDEBUG + SB_DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without " + "calling Release()"; +#endif +} + +void RefCountedThreadSafeBase::AddRef() const { +#ifndef NDEBUG + SB_DCHECK(!in_dtor_); +#endif + SbAtomicNoBarrier_Increment(&ref_count_, 1); +} + +bool RefCountedThreadSafeBase::Release() const { +#ifndef NDEBUG + SB_DCHECK(!in_dtor_); + SB_DCHECK(!(SbAtomicAcquire_Load(&ref_count_) == 0)); +#endif + if (SbAtomicBarrier_Increment(&ref_count_, -1) == 0) { +#ifndef NDEBUG + in_dtor_ = true; +#endif + return true; + } + return false; +} + +} // namespace subtle + +} // namespace starboard
diff --git a/src/starboard/common/ref_counted.h b/src/starboard/common/ref_counted.h new file mode 100644 index 0000000..0ab74db --- /dev/null +++ b/src/starboard/common/ref_counted.h
@@ -0,0 +1,288 @@ +// 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. + +#ifndef STARBOARD_COMMON_REF_COUNTED_H_ +#define STARBOARD_COMMON_REF_COUNTED_H_ + +#include "starboard/atomic.h" +#include "starboard/common/thread_collision_warner.h" +#include "starboard/log.h" + +namespace starboard { + +namespace subtle { + +class RefCountedBase { + public: + bool HasOneRef() const { return ref_count_ == 1; } + + protected: + RefCountedBase(); + ~RefCountedBase(); + + void AddRef() const; + + // Returns true if the object should self-delete. + bool Release() const; + + private: + mutable int ref_count_; +#ifndef NDEBUG + mutable bool in_dtor_; +#endif + + DFAKE_MUTEX(add_release_); +}; + +class RefCountedThreadSafeBase { + public: + bool HasOneRef() const; + + protected: + RefCountedThreadSafeBase(); + ~RefCountedThreadSafeBase(); + + void AddRef() const; + + // Returns true if the object should self-delete. + bool Release() const; + + private: + mutable SbAtomic32 ref_count_; +#ifndef NDEBUG + mutable bool in_dtor_; +#endif +}; + +} // namespace subtle + +// +// A base class for reference counted classes. Otherwise, known as a cheap +// knock-off of WebKit's RefCounted<T> class. To use this guy just extend your +// class from it like so: +// +// class MyFoo : public starboard::RefCounted<MyFoo> { +// ... +// private: +// friend class starboard::RefCounted<MyFoo>; +// ~MyFoo(); +// }; +// +// You should always make your destructor private, to avoid any code deleting +// the object accidently while there are references to it. +template <class T> +class RefCounted : public subtle::RefCountedBase { + public: + RefCounted() {} + + void AddRef() const { subtle::RefCountedBase::AddRef(); } + + void Release() const { + if (subtle::RefCountedBase::Release()) { + delete static_cast<const T*>(this); + } + } + + protected: + ~RefCounted() {} +}; + +// Forward declaration. +template <class T, typename Traits> +class RefCountedThreadSafe; + +// Default traits for RefCountedThreadSafe<T>. Deletes the object when its ref +// count reaches 0. Overload to delete it on a different thread etc. +template <typename T> +struct DefaultRefCountedThreadSafeTraits { + static void Destruct(const T* x) { + // Delete through RefCountedThreadSafe to make child classes only need to be + // friend with RefCountedThreadSafe instead of this struct, which is an + // implementation detail. + RefCountedThreadSafe<T, DefaultRefCountedThreadSafeTraits>::DeleteInternal( + x); + } +}; + +// +// A thread-safe variant of RefCounted<T> +// +// class MyFoo : public starboard::RefCountedThreadSafe<MyFoo> { +// ... +// }; +// +// If you're using the default trait, then you should add compile time +// asserts that no one else is deleting your object. i.e. +// private: +// friend class starboard::RefCountedThreadSafe<MyFoo>; +// ~MyFoo(); +template <class T, typename Traits = DefaultRefCountedThreadSafeTraits<T> > +class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase { + public: + RefCountedThreadSafe() {} + + void AddRef() const { subtle::RefCountedThreadSafeBase::AddRef(); } + + void Release() const { + if (subtle::RefCountedThreadSafeBase::Release()) { + Traits::Destruct(static_cast<const T*>(this)); + } + } + + protected: + ~RefCountedThreadSafe() {} + + private: + friend struct DefaultRefCountedThreadSafeTraits<T>; + static void DeleteInternal(const T* x) { delete x; } +}; + +// +// A thread-safe wrapper for some piece of data so we can place other +// things in scoped_refptrs<>. +// +template <typename T> +class RefCountedData : public starboard::RefCountedThreadSafe<starboard::RefCountedData<T> > { + public: + RefCountedData() : data() {} + RefCountedData(const T& in_value) : data(in_value) {} + + T data; + + private: + friend class starboard::RefCountedThreadSafe<starboard::RefCountedData<T> >; + ~RefCountedData() {} +}; + +// +// A smart pointer class for reference counted objects. Use this class instead +// of calling AddRef and Release manually on a reference counted object to +// avoid common memory leaks caused by forgetting to Release an object +// reference. Sample usage: +// +// class MyFoo : public RefCounted<MyFoo> { +// ... +// }; +// +// void some_function() { +// scoped_refptr<MyFoo> foo = new MyFoo(); +// foo->Method(param); +// // |foo| is released when this function returns +// } +// +// void some_other_function() { +// scoped_refptr<MyFoo> foo = new MyFoo(); +// ... +// foo = NULL; // explicitly releases |foo| +// ... +// if (foo) +// foo->Method(param); +// } +// +// The above examples show how scoped_refptr<T> acts like a pointer to T. +// Given two scoped_refptr<T> classes, it is also possible to exchange +// references between the two objects, like so: +// +// { +// scoped_refptr<MyFoo> a = new MyFoo(); +// scoped_refptr<MyFoo> b; +// +// b.swap(a); +// // now, |b| references the MyFoo object, and |a| references NULL. +// } +// +// To make both |a| and |b| in the above example reference the same MyFoo +// object, simply use the assignment operator: +// +// { +// scoped_refptr<MyFoo> a = new MyFoo(); +// scoped_refptr<MyFoo> b; +// +// b = a; +// // now, |a| and |b| each own a reference to the same MyFoo object. +// } +// +template <class T> +class scoped_refptr { + public: + typedef T element_type; + + scoped_refptr() : ptr_(NULL) {} + + scoped_refptr(T* p) : ptr_(p) { + if (ptr_) + ptr_->AddRef(); + } + + scoped_refptr(const scoped_refptr<T>& r) : ptr_(r.ptr_) { + if (ptr_) + ptr_->AddRef(); + } + + template <typename U> + scoped_refptr(const scoped_refptr<U>& r) + : ptr_(r.get()) { + if (ptr_) + ptr_->AddRef(); + } + + ~scoped_refptr() { + if (ptr_) + ptr_->Release(); + } + + T* get() const { return ptr_; } + operator T*() const { return ptr_; } + T* operator->() const { + SB_DCHECK(ptr_ != NULL); + return ptr_; + } + + T& operator*() const { + SB_DCHECK(ptr_ != NULL); + return *ptr_; + } + + scoped_refptr<T>& operator=(T* p) { + // AddRef first so that self assignment should work + if (p) + p->AddRef(); + T* old_ptr = ptr_; + ptr_ = p; + if (old_ptr) + old_ptr->Release(); + return *this; + } + + scoped_refptr<T>& operator=(const scoped_refptr<T>& r) { + return * this = r.ptr_; + } + + template <typename U> + scoped_refptr<T>& operator=(const scoped_refptr<U>& r) { + return * this = r.get(); + } + + void swap(T** pp) { + T* p = ptr_; + ptr_ = *pp; + *pp = p; + } + + void swap(scoped_refptr<T>& r) { swap(&r.ptr_); } + + protected: + T* ptr_; +}; + +// Handy utility for creating a scoped_refptr<T> out of a T* explicitly without +// having to retype all the template arguments +template <typename T> +scoped_refptr<T> make_scoped_refptr(T* t) { + return scoped_refptr<T>(t); +} + +} // namespace starboard + +#endif // STARBOARD_COMMON_REF_COUNTED_H_
diff --git a/src/starboard/common/reset_and_return.h b/src/starboard/common/reset_and_return.h new file mode 100644 index 0000000..c881e95 --- /dev/null +++ b/src/starboard/common/reset_and_return.h
@@ -0,0 +1,31 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_COMMON_RESET_AND_RETURN_H_ +#define STARBOARD_COMMON_RESET_AND_RETURN_H_ + +namespace starboard { +namespace common { + +template <typename T> +T ResetAndReturn(T* t) { + T result(*t); + *t = T(); + return result; +} + +} // namespace common +} // namespace starboard + +#endif // STARBOARD_COMMON_RESET_AND_RETURN_H_
diff --git a/src/starboard/common/scoped_ptr.h b/src/starboard/common/scoped_ptr.h new file mode 100644 index 0000000..b62f1c6 --- /dev/null +++ b/src/starboard/common/scoped_ptr.h
@@ -0,0 +1,491 @@ +// 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. + +// Scopers help you manage ownership of a pointer, helping you easily manage the +// a pointer within a scope, and automatically destroying the pointer at the +// end of a scope. There are two main classes you will use, which correspond +// to the operators new/delete and new[]/delete[]. +// +// Example usage (scoped_ptr): +// { +// scoped_ptr<Foo> foo(new Foo("wee")); +// } // foo goes out of scope, releasing the pointer with it. +// +// { +// scoped_ptr<Foo> foo; // No pointer managed. +// foo.reset(new Foo("wee")); // Now a pointer is managed. +// foo.reset(new Foo("wee2")); // Foo("wee") was destroyed. +// foo.reset(new Foo("wee3")); // Foo("wee2") was destroyed. +// foo->Method(); // Foo::Method() called. +// foo.get()->Method(); // Foo::Method() called. +// SomeFunc(foo.release()); // SomeFunc takes ownership, foo no longer +// // manages a pointer. +// foo.reset(new Foo("wee4")); // foo manages a pointer again. +// foo.reset(); // Foo("wee4") destroyed, foo no longer +// // manages a pointer. +// } // foo wasn't managing a pointer, so nothing was destroyed. +// +// Example usage (scoped_array): +// { +// scoped_array<Foo> foo(new Foo[100]); +// foo.get()->Method(); // Foo::Method on the 0th element. +// foo[10].Method(); // Foo::Method on the 10th element. +// } +// +// These scopers also implement part of the functionality of C++11 unique_ptr +// in that they are "movable but not copyable." You can use the scopers in +// the parameter and return types of functions to signify ownership transfer +// in to and out of a function. When calling a function that has a scoper +// as the argument type, it must be called with the result of an analogous +// scoper's Pass() function or another function that generates a temporary; +// passing by copy will NOT work. Here is an example using scoped_ptr: +// +// void TakesOwnership(scoped_ptr<Foo> arg) { +// // Do something with arg +// } +// scoped_ptr<Foo> CreateFoo() { +// // No need for calling Pass() because we are constructing a temporary +// // for the return value. +// return scoped_ptr<Foo>(new Foo("new")); +// } +// scoped_ptr<Foo> PassThru(scoped_ptr<Foo> arg) { +// return arg.Pass(); +// } +// +// { +// scoped_ptr<Foo> ptr(new Foo("yay")); // ptr manages Foo("yay"). +// TakesOwnership(ptr.Pass()); // ptr no longer owns Foo("yay"). +// scoped_ptr<Foo> ptr2 = CreateFoo(); // ptr2 owns the return Foo. +// scoped_ptr<Foo> ptr3 = // ptr3 now owns what was in ptr2. +// PassThru(ptr2.Pass()); // ptr2 is correspondingly NULL. +// } +// +// Notice that if you do not call Pass() when returning from PassThru(), or +// when invoking TakesOwnership(), the code will not compile because scopers +// are not copyable; they only implement move semantics which require calling +// the Pass() function to signify a destructive transfer of state. CreateFoo() +// is different though because we are constructing a temporary on the return +// line and thus can avoid needing to call Pass(). +// +// Pass() properly handles upcast in assignment, i.e. you can assign +// scoped_ptr<Child> to scoped_ptr<Parent>: +// +// scoped_ptr<Foo> foo(new Foo()); +// scoped_ptr<FooParent> parent = foo.Pass(); +// +// PassAs<>() should be used to upcast return value in return statement: +// +// scoped_ptr<Foo> CreateFoo() { +// scoped_ptr<FooChild> result(new FooChild()); +// return result.PassAs<Foo>(); +// } +// +// Note that PassAs<>() is implemented only for scoped_ptr, but not for +// scoped_array. This is because casting array pointers may not be safe. + +#ifndef STARBOARD_COMMON_SCOPED_PTR_H_ +#define STARBOARD_COMMON_SCOPED_PTR_H_ + +// This is an implementation designed to match the anticipated future TR2 +// implementation of the scoped_ptr class, and its closely-related brethren, +// scoped_array, scoped_ptr_malloc. + +#include "starboard/log.h" +#include "starboard/memory.h" +#include "starboard/types.h" + +#include <algorithm> + +#include "starboard/common/move.h" + +namespace starboard { + +// A scoped_ptr<T> is like a T*, except that the destructor of scoped_ptr<T> +// automatically deletes the pointer it holds (if any). +// That is, scoped_ptr<T> owns the T object that it points to. +// Like a T*, a scoped_ptr<T> may hold either NULL or a pointer to a T object. +// Also like T*, scoped_ptr<T> is thread-compatible, and once you +// dereference it, you get the thread safety guarantees of T. +// +// The size of a scoped_ptr is small: +// sizeof(scoped_ptr<C>) == sizeof(C*) +template <class C> +class scoped_ptr { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr, RValue) + + public: + // The element type + typedef C element_type; + + // Constructor. Defaults to initializing with NULL. + // There is no way to create an uninitialized scoped_ptr. + // The input parameter must be allocated with new. + explicit scoped_ptr(C* p = NULL) : ptr_(p) {} + +// The GHS compiler always chooses this copy constructor over the next one, +// so disable this to promote the more important and frequently used constr. +#if !defined(COMPILER_GHS) + // Constructor. Allows construction from a scoped_ptr rvalue for a + // convertible type. + template <typename U> + scoped_ptr(scoped_ptr<U> other) + : ptr_(other.release()) {} +#endif + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr(RValue rvalue) : ptr_(rvalue.object->release()) {} + + // Destructor. If there is a C object, delete it. + // We don't need to test ptr_ == NULL because C++ does that for us. + ~scoped_ptr() { + enum { type_must_be_complete = sizeof(C) }; + delete ptr_; + } + + // operator=. Allows assignment from a scoped_ptr rvalue for a convertible + // type. + template <typename U> + scoped_ptr& operator=(scoped_ptr<U> rhs) { + reset(rhs.release()); + return *this; + } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr& operator=(RValue rhs) { + swap(*rhs->object); + return *this; + } + + // Reset. Deletes the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (p != ptr_) { + enum { type_must_be_complete = sizeof(C) }; + delete ptr_; + ptr_ = p; + } + } + + // Accessors to get the owned object. + // operator* and operator-> will SB_DCHECK() if there is no current object. + C& operator*() const { + SB_DCHECK(ptr_ != NULL); + return *ptr_; + } + C* operator->() const { + SB_DCHECK(ptr_ != NULL); + return ptr_; + } + C* get() const { return ptr_; } + + // Allow scoped_ptr<C> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + typedef C* scoped_ptr::*Testable; + operator Testable() const { return ptr_ ? &scoped_ptr::ptr_ : NULL; } + + // Comparison operators. + // These return whether two scoped_ptr refer to the same object, not just to + // two different but equal objects. + bool operator==(C* p) const { return ptr_ == p; } + bool operator!=(C* p) const { return ptr_ != p; } + + // Swap two scoped pointers. + void swap(scoped_ptr& p2) { + C* tmp = ptr_; + ptr_ = p2.ptr_; + p2.ptr_ = tmp; + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() { + C* retVal = ptr_; + ptr_ = NULL; + return retVal; + } + + template <typename PassAsType> + scoped_ptr<PassAsType> PassAs() { + return scoped_ptr<PassAsType>(release()); + } + + private: + C* ptr_; + + // Forbid comparison of scoped_ptr types. If C2 != C, it totally doesn't + // make sense, and if C2 == C, it still doesn't make sense because you should + // never have the same object owned by two different scoped_ptrs. + template <class C2> + bool operator==(scoped_ptr<C2> const& p2) const; + template <class C2> + bool operator!=(scoped_ptr<C2> const& p2) const; +}; + +// Free functions +template <class C> +void swap(scoped_ptr<C>& p1, scoped_ptr<C>& p2) { + p1.swap(p2); +} + +template <class C> +bool operator==(C* p1, const scoped_ptr<C>& p2) { + return p1 == p2.get(); +} + +template <class C> +bool operator!=(C* p1, const scoped_ptr<C>& p2) { + return p1 != p2.get(); +} + +// scoped_array<C> is like scoped_ptr<C>, except that the caller must allocate +// with new [] and the destructor deletes objects with delete []. +// +// As with scoped_ptr<C>, a scoped_array<C> either points to an object +// or is NULL. A scoped_array<C> owns the object that it points to. +// scoped_array<T> is thread-compatible, and once you index into it, +// the returned objects have only the thread safety guarantees of T. +// +// Size: sizeof(scoped_array<C>) == sizeof(C*) +template <class C> +class scoped_array { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_array, RValue) + + public: + // The element type + typedef C element_type; + + // Constructor. Defaults to initializing with NULL. + // There is no way to create an uninitialized scoped_array. + // The input parameter must be allocated with new []. + explicit scoped_array(C* p = NULL) : array_(p) {} + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_array(RValue rvalue) : array_(rvalue.object->release()) {} + + // Destructor. If there is a C object, delete it. + // We don't need to test ptr_ == NULL because C++ does that for us. + ~scoped_array() { + enum { type_must_be_complete = sizeof(C) }; + delete[] array_; + } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_array& operator=(RValue rhs) { + swap(*rhs.object); + return *this; + } + + // Reset. Deletes the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (p != array_) { + enum { type_must_be_complete = sizeof(C) }; + delete[] array_; + array_ = p; + } + } + + // Get one element of the current object. + // Will SB_DCHECK() if there is no current object, or index i is negative. + C& operator[](ptrdiff_t i) const { + SB_DCHECK(i >= 0); + SB_DCHECK(array_ != NULL); + return array_[i]; + } + + // Get a pointer to the zeroth element of the current object. + // If there is no current object, return NULL. + C* get() const { return array_; } + + // Allow scoped_array<C> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + typedef C* scoped_array::*Testable; + operator Testable() const { return array_ ? &scoped_array::array_ : NULL; } + + // Comparison operators. + // These return whether two scoped_array refer to the same object, not just to + // two different but equal objects. + bool operator==(C* p) const { return array_ == p; } + bool operator!=(C* p) const { return array_ != p; } + + // Swap two scoped arrays. + void swap(scoped_array& p2) { + C* tmp = array_; + array_ = p2.array_; + p2.array_ = tmp; + } + + // Release an array. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() { + C* retVal = array_; + array_ = NULL; + return retVal; + } + + private: + C* array_; + + // Forbid comparison of different scoped_array types. + template <class C2> + bool operator==(scoped_array<C2> const& p2) const; + template <class C2> + bool operator!=(scoped_array<C2> const& p2) const; +}; + +// Free functions +template <class C> +void swap(scoped_array<C>& p1, scoped_array<C>& p2) { + p1.swap(p2); +} + +template <class C> +bool operator==(C* p1, const scoped_array<C>& p2) { + return p1 == p2.get(); +} + +template <class C> +bool operator!=(C* p1, const scoped_array<C>& p2) { + return p1 != p2.get(); +} + +// This class wraps the c library function free() in a class that can be +// passed as a template argument to scoped_ptr_malloc below. +class ScopedPtrMallocFree { + public: + inline void operator()(void* x) const { SbMemoryDeallocate(x); } +}; + +// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a +// second template argument, the functor used to free the object. + +template <class C, class FreeProc = ScopedPtrMallocFree> +class scoped_ptr_malloc { + MOVE_ONLY_TYPE_FOR_CPP_03(scoped_ptr_malloc, RValue) + + public: + // The element type + typedef C element_type; + + // Constructor. Defaults to initializing with NULL. + // There is no way to create an uninitialized scoped_ptr. + // The input parameter must be allocated with an allocator that matches the + // Free functor. For the default Free functor, this is malloc, calloc, or + // realloc. + explicit scoped_ptr_malloc(C* p = NULL) : ptr_(p) {} + + // Constructor. Move constructor for C++03 move emulation of this type. + scoped_ptr_malloc(RValue rvalue) : ptr_(rvalue.object->release()) {} + + // Destructor. If there is a C object, call the Free functor. + ~scoped_ptr_malloc() { reset(); } + + // operator=. Move operator= for C++03 move emulation of this type. + scoped_ptr_malloc& operator=(RValue rhs) { + swap(*rhs.object); + return *this; + } + + // Reset. Calls the Free functor on the current owned object, if any. + // Then takes ownership of a new object, if given. + // this->reset(this->get()) works. + void reset(C* p = NULL) { + if (ptr_ != p) { + FreeProc free_proc; + free_proc(ptr_); + ptr_ = p; + } + } + + // Get the current object. + // operator* and operator-> will cause an SB_DCHECK() failure if there is + // no current object. + C& operator*() const { + SB_DCHECK(ptr_ != NULL); + return *ptr_; + } + + C* operator->() const { + SB_DCHECK(ptr_ != NULL); + return ptr_; + } + + C* get() const { return ptr_; } + + // Allow scoped_ptr_malloc<C> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + typedef C* scoped_ptr_malloc::*Testable; + operator Testable() const { return ptr_ ? &scoped_ptr_malloc::ptr_ : NULL; } + + // Comparison operators. + // These return whether a scoped_ptr_malloc and a plain pointer refer + // to the same object, not just to two different but equal objects. + // For compatibility with the boost-derived implementation, these + // take non-const arguments. + bool operator==(C* p) const { return ptr_ == p; } + + bool operator!=(C* p) const { return ptr_ != p; } + + // Swap two scoped pointers. + void swap(scoped_ptr_malloc& b) { + C* tmp = b.ptr_; + b.ptr_ = ptr_; + ptr_ = tmp; + } + + // Release a pointer. + // The return value is the current pointer held by this object. + // If this object holds a NULL pointer, the return value is NULL. + // After this operation, this object will hold a NULL pointer, + // and will not own the object any more. + C* release() { + C* tmp = ptr_; + ptr_ = NULL; + return tmp; + } + + private: + C* ptr_; + + // no reason to use these: each scoped_ptr_malloc should have its own object + template <class C2, class GP> + bool operator==(scoped_ptr_malloc<C2, GP> const& p) const; + template <class C2, class GP> + bool operator!=(scoped_ptr_malloc<C2, GP> const& p) const; +}; + +template <class C, class FP> +inline void swap(scoped_ptr_malloc<C, FP>& a, scoped_ptr_malloc<C, FP>& b) { + a.swap(b); +} + +template <class C, class FP> +inline bool operator==(C* p, const scoped_ptr_malloc<C, FP>& b) { + return p == b.get(); +} + +template <class C, class FP> +inline bool operator!=(C* p, const scoped_ptr_malloc<C, FP>& b) { + return p != b.get(); +} + +// A function to convert T* into scoped_ptr<T> +// Doing e.g. make_scoped_ptr(new FooBarBaz<type>(arg)) is a shorter notation +// for scoped_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg)) +template <typename T> +scoped_ptr<T> make_scoped_ptr(T* ptr) { + return scoped_ptr<T>(ptr); +} + +} // namespace starboard + +#endif // STARBOARD_COMMON_SCOPED_PTR_H_
diff --git a/src/starboard/nplb/main.cc b/src/starboard/common/test_main.cc similarity index 100% rename from src/starboard/nplb/main.cc rename to src/starboard/common/test_main.cc
diff --git a/src/starboard/common/thread_collision_warner.cc b/src/starboard/common/thread_collision_warner.cc new file mode 100644 index 0000000..5a9e853 --- /dev/null +++ b/src/starboard/common/thread_collision_warner.cc
@@ -0,0 +1,63 @@ +// Copyright (c) 2010 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 "starboard/common/thread_collision_warner.h" + +#include "starboard/atomic.h" +#include "starboard/log.h" +#include "starboard/thread.h" +#include "starboard/types.h" + +namespace starboard { + +void DCheckAsserter::warn() { + SB_NOTREACHED() << "Thread Collision"; +} + +static SbAtomic32 CurrentThread() { + const SbThreadId current_thread_id = SbThreadGetId(); + // We need to get the thread id into an atomic data type. This might be a + // truncating conversion, but any loss-of-information just increases the + // chance of a false negative, not a false positive. + const SbAtomic32 atomic_thread_id = + static_cast<SbAtomic32>(current_thread_id); + return atomic_thread_id; +} + +void ThreadCollisionWarner::EnterSelf() { + // If the active thread is 0 then I'll write the current thread ID + // if two or more threads arrive here only one will succeed to + // write on valid_thread_id_ the current thread ID. + SbAtomic32 current_thread_id = CurrentThread(); + + int previous_value = + SbAtomicNoBarrier_CompareAndSwap(&valid_thread_id_, 0, current_thread_id); + if (previous_value != 0 && previous_value != current_thread_id) { + // gotcha! a thread is trying to use the same class and that is + // not current thread. + asserter_->warn(); + } + + SbAtomicNoBarrier_Increment(&counter_, 1); +} + +void ThreadCollisionWarner::Enter() { + SbAtomic32 current_thread_id = CurrentThread(); + + if (SbAtomicNoBarrier_CompareAndSwap(&valid_thread_id_, 0, + current_thread_id) != 0) { + // gotcha! another thread is trying to use the same class. + asserter_->warn(); + } + + SbAtomicNoBarrier_Increment(&counter_, 1); +} + +void ThreadCollisionWarner::Leave() { + if (SbAtomicBarrier_Increment(&counter_, -1) == 0) { + SbAtomicNoBarrier_Store(&valid_thread_id_, 0); + } +} + +} // namespace starboard
diff --git a/src/starboard/common/thread_collision_warner.h b/src/starboard/common/thread_collision_warner.h new file mode 100644 index 0000000..4aefb21 --- /dev/null +++ b/src/starboard/common/thread_collision_warner.h
@@ -0,0 +1,220 @@ +// 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. + +#ifndef STARBOARD_COMMON_THREAD_COLLISION_WARNER_H_ +#define STARBOARD_COMMON_THREAD_COLLISION_WARNER_H_ + +#include "starboard/atomic.h" + +// A helper class alongside macros to be used to verify assumptions about thread +// safety of a class. +// +// Example: Queue implementation non thread-safe but still usable if clients +// are synchronized somehow. +// +// In this case the macro DFAKE_SCOPED_LOCK has to be +// used, it checks that if a thread is inside the push/pop then +// noone else is still inside the pop/push +// +// class NonThreadSafeQueue { +// public: +// ... +// void push(int) { DFAKE_SCOPED_LOCK(push_pop_); ... } +// int pop() { DFAKE_SCOPED_LOCK(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Queue implementation non thread-safe but still usable if clients +// are synchronized somehow, it calls a method to "protect" from +// a "protected" method +// +// In this case the macro DFAKE_SCOPED_RECURSIVE_LOCK +// has to be used, it checks that if a thread is inside the push/pop +// then noone else is still inside the pop/push +// +// class NonThreadSafeQueue { +// public: +// void push(int) { +// DFAKE_SCOPED_LOCK(push_pop_); +// ... +// } +// int pop() { +// DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); +// bar(); +// ... +// } +// void bar() { DFAKE_SCOPED_RECURSIVE_LOCK(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Queue implementation not usable even if clients are synchronized, +// so only one thread in the class life cycle can use the two members +// push/pop. +// +// In this case the macro DFAKE_SCOPED_LOCK_THREAD_LOCKED pins the +// specified +// critical section the first time a thread enters push or pop, from +// that time on only that thread is allowed to execute push or pop. +// +// class NonThreadSafeQueue { +// public: +// ... +// void push(int) { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... } +// int pop() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(push_pop_); ... } +// ... +// private: +// DFAKE_MUTEX(push_pop_); +// }; +// +// +// Example: Class that has to be contructed/destroyed on same thread, it has +// a "shareable" method (with external synchronization) and a not +// shareable method (even with external synchronization). +// +// In this case 3 Critical sections have to be defined +// +// class ExoticClass { +// public: +// ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// ~ExoticClass() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// +// void Shareable() { DFAKE_SCOPED_LOCK(shareable_section_); ... } +// void NotShareable() { DFAKE_SCOPED_LOCK_THREAD_LOCKED(ctor_dtor_); ... } +// ... +// private: +// DFAKE_MUTEX(ctor_dtor_); +// DFAKE_MUTEX(shareable_section_); +// }; + +#if !defined(NDEBUG) + +// Defines a class member that acts like a mutex. It is used only as a +// verification tool. +#define DFAKE_MUTEX(obj) mutable starboard::ThreadCollisionWarner obj +// Asserts the call is never called simultaneously in two threads. Used at +// member function scope. +#define DFAKE_SCOPED_LOCK(obj) \ + starboard::ThreadCollisionWarner::ScopedCheck s_check_##obj(&obj) +// Asserts the call is never called simultaneously in two threads. Used at +// member function scope. Same as DFAKE_SCOPED_LOCK but allows recursive locks. +#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) \ + starboard::ThreadCollisionWarner::ScopedRecursiveCheck sr_check_##obj(&obj) +// Asserts the code is always executed in the same thread. +#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) \ + starboard::ThreadCollisionWarner::Check check_##obj(&obj) + +#else + +#define DFAKE_MUTEX(obj) typedef void InternalFakeMutexType##obj +#define DFAKE_SCOPED_LOCK(obj) ((void)0) +#define DFAKE_SCOPED_RECURSIVE_LOCK(obj) ((void)0) +#define DFAKE_SCOPED_LOCK_THREAD_LOCKED(obj) ((void)0) + +#endif + +namespace starboard { + +// The class ThreadCollisionWarner uses an Asserter to notify the collision +// AsserterBase is the interfaces and DCheckAsserter is the default asserter +// used. During the unit tests is used another class that doesn't "DCHECK" +// in case of collision (check thread_collision_warner_unittests.cc) +struct AsserterBase { + virtual ~AsserterBase() {} + virtual void warn() = 0; +}; + +struct DCheckAsserter : public AsserterBase { + virtual ~DCheckAsserter() {} + virtual void warn(); +}; + +class ThreadCollisionWarner { + public: + // The parameter asserter is there only for test purpose + ThreadCollisionWarner(AsserterBase* asserter = new DCheckAsserter()) + : valid_thread_id_(0), counter_(0), asserter_(asserter) {} + + ~ThreadCollisionWarner() { delete asserter_; } + + // This class is meant to be used through the macro + // DFAKE_SCOPED_LOCK_THREAD_LOCKED + // it doesn't leave the critical section, as opposed to ScopedCheck, + // because the critical section being pinned is allowed to be used only + // from one thread + class Check { + public: + explicit Check(ThreadCollisionWarner* warner) : warner_(warner) { + warner_->EnterSelf(); + } + + ~Check() {} + + private: + ThreadCollisionWarner* warner_; + }; + + // This class is meant to be used through the macro + // DFAKE_SCOPED_LOCK + class ScopedCheck { + public: + explicit ScopedCheck(ThreadCollisionWarner* warner) : warner_(warner) { + warner_->Enter(); + } + + ~ScopedCheck() { warner_->Leave(); } + + private: + ThreadCollisionWarner* warner_; + }; + + // This class is meant to be used through the macro + // DFAKE_SCOPED_RECURSIVE_LOCK + class ScopedRecursiveCheck { + public: + explicit ScopedRecursiveCheck(ThreadCollisionWarner* warner) + : warner_(warner) { + warner_->EnterSelf(); + } + + ~ScopedRecursiveCheck() { warner_->Leave(); } + + private: + ThreadCollisionWarner* warner_; + }; + + private: + // This method stores the current thread identifier and does a DCHECK + // if a another thread has already done it, it is safe if same thread + // calls this multiple time (recursion allowed). + void EnterSelf(); + + // Same as EnterSelf but recursion is not allowed. + void Enter(); + + // Removes the thread_id stored in order to allow other threads to + // call EnterSelf or Enter. + void Leave(); + + // This stores the thread id that is inside the critical section, if the + // value is 0 then no thread is inside. + volatile SbAtomic32 valid_thread_id_; + + // Counter to trace how many time a critical section was "pinned" + // (when allowed) in order to unpin it when counter_ reaches 0. + volatile SbAtomic32 counter_; + + // Here only for class unit tests purpose, during the test I need to not + // DCHECK but notify the collision with something else. + AsserterBase* asserter_; +}; + +} // namespace starboard + +#endif // STARBOARD_COMMON_THREAD_COLLISION_WARNER_H_
diff --git a/src/starboard/condition_variable.h b/src/starboard/condition_variable.h index 463f523..4c519ba 100644 --- a/src/starboard/condition_variable.h +++ b/src/starboard/condition_variable.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Condition variables. +// Module Overview: Starboard Condition Variable module +// +// Defines an interface for condition variables. #ifndef STARBOARD_CONDITION_VARIABLE_H_ #define STARBOARD_CONDITION_VARIABLE_H_ @@ -51,18 +53,18 @@ } // Creates a new condition variable to work with |opt_mutex|, which may be null, -// placing the newly created condition variable in |out_condition|. Returns -// whether the condition variable could be created. -// TODO: It looks like WTF does not have the mutex available when creating -// the condition variable, and pthreads doesn't appear to require the mutex on -// condvar creation, so we should just remove the parameter. +// placing the newly created condition variable in |out_condition|. +// +// The return value indicates whether the condition variable could be created. SB_EXPORT bool SbConditionVariableCreate(SbConditionVariable* out_condition, SbMutex* opt_mutex); -// Destroys a condition variable, returning whether the destruction was -// successful. The condition variable specified by |condition| is -// invalidated. Behavior is undefined if other threads are currently waiting -// on this condition variable. +// Destroys the specified SbConditionVariable. The return value indicates +// whether the destruction was successful. The behavior is undefined if other +// threads are currently waiting on this condition variable. +// +// |condition|: The SbConditionVariable to be destroyed. This invalidates the +// condition variable. SB_EXPORT bool SbConditionVariableDestroy(SbConditionVariable* condition); // Waits for |condition|, releasing the held lock |mutex|, blocking @@ -72,19 +74,30 @@ SbConditionVariableWait(SbConditionVariable* condition, SbMutex* mutex); // Waits for |condition|, releasing the held lock |mutex|, blocking up to -// |timeout_duration|, and returning the acquisition result. If -// |timeout_duration| is less than or equal to zero, it will return as quickly -// as possible with a kSbConditionVariableTimedOut result. Behavior is undefined -// if |mutex| is not held. +// |timeout_duration|, and returning the acquisition result. Behavior is +// undefined if |mutex| is not held. +// +// |timeout_duration|: The maximum amount of time that function should wait +// for |condition|. If the |timeout_duration| value is less than or equal to +// zero, the function returns as quickly as possible with a +// kSbConditionVariableTimedOut result. SB_EXPORT SbConditionVariableResult SbConditionVariableWaitTimed(SbConditionVariable* condition, SbMutex* mutex, SbTime timeout_duration); -// Broadcasts to all current waiters of |condition| to stop waiting. +// Broadcasts to all current waiters of |condition| to stop waiting. This +// function wakes all of the threads waiting on |condition| while +// SbConditionVariableSignal wakes a single thread. +// +// |condition|: The condition that should no longer be waited for. SB_EXPORT bool SbConditionVariableBroadcast(SbConditionVariable* condition); -// Signals the next waiter of |condition| to stop waiting. +// Signals the next waiter of |condition| to stop waiting. This function wakes +// a single thread waiting on |condition| while SbConditionVariableBroadcast +// wakes all threads waiting on it. +// +// |condition|: The condition that the waiter should stop waiting for. SB_EXPORT bool SbConditionVariableSignal(SbConditionVariable* condition); #ifdef __cplusplus
diff --git a/src/starboard/configuration.h b/src/starboard/configuration.h index 94538d7..d21e420 100644 --- a/src/starboard/configuration.h +++ b/src/starboard/configuration.h
@@ -12,13 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -// A description of the current platform in lurid detail such that common code -// never needs to actually know what the current operating system and -// architecture are. It is both very pragmatic and canonical in that if any -// application code finds itself needing to make a platform decision, it should -// always define a Starboard Configuration feature instead. This implies the -// continued existence of very narrowly-defined configuration features, but it -// retains porting control in Starboard. +// Module Overview: Starboard Configuration module +// +// Provides a description of the current platform in lurid detail so that +// common code never needs to actually know what the current operating system +// and architecture are. +// +// It is both very pragmatic and canonical in that if any application code +// finds itself needing to make a platform decision, it should always define +// a Starboard Configuration feature instead. This implies the continued +// existence of very narrowly-defined configuration features, but it retains +// porting control in Starboard. #ifndef STARBOARD_CONFIGURATION_H_ #define STARBOARD_CONFIGURATION_H_ @@ -35,7 +39,12 @@ // The maximum API version allowed by this version of the Starboard headers, // inclusive. -#define SB_MAXIMUM_API_VERSION 1 +#define SB_MAXIMUM_API_VERSION 3 + +// The API version that is currently open for changes, and therefore is not +// stable or frozen. Production-oriented ports should avoid declaring that they +// implement the experimental Starboard API version. +#define SB_EXPERIMENTAL_API_VERSION 3 // --- Common Detected Features ---------------------------------------------- @@ -186,6 +195,26 @@ #endif // SB_IS(COMPILER_MSVC) #endif // !defined(SB_UNLIKELY) +// SB_DEPRECATED(int Foo(int bar)); +// Annotates the function as deprecated, which will trigger a compiler +// warning when referenced. +#if SB_IS(COMPILER_GCC) +#define SB_DEPRECATED(FUNC) FUNC __attribute__((deprecated)) +#elif SB_IS(COMPILER_MSVC) +#define SB_DEPRECATED(FUNC) __declspec(deprecated) FUNC +#else +// Empty definition for other compilers. +#define SB_DEPRECATED(FUNC) FUNC +#endif + +// SB_DEPRECATED_EXTERNAL(...) annotates the function as deprecated for +// external clients, but not deprecated for starboard. +#if defined(STARBOARD_IMPLEMENTATION) +#define SB_DEPRECATED_EXTERNAL(FUNC) FUNC +#else +#define SB_DEPRECATED_EXTERNAL(FUNC) SB_DEPRECATED(FUNC) +#endif + // A macro to disallow the copy constructor and operator= functions // This should be used in the private: declarations for a class #define SB_DISALLOW_COPY_AND_ASSIGN(TypeName) \ @@ -336,6 +365,14 @@ #error "Your platform must define SB_MAX_THREAD_NAME_LENGTH." #endif +#if SB_VERSION(2) && !defined(SB_HAS_MICROPHONE) +#error "Your platform must define SB_HAS_MICROPHONE in API versions 2 or later." +#endif + +#if SB_VERSION(3) && !defined(SB_HAS_TIME_THREAD_NOW) +#error "Your platform must define SB_HAS_TIME_THREAD_NOW in API 3 or later." +#endif + #if SB_HAS(PLAYER) #if !SB_IS(PLAYER_COMPOSITED) && !SB_IS(PLAYER_PUNCHED_OUT) && \ !SB_IS(PLAYER_PRODUCING_TEXTURE) @@ -364,6 +401,10 @@ #error "Only one SB_HAS_{MANY, 1, 2, 4, 6}_CORE[S] can be defined per platform." #endif +#if !defined(SB_HAS_THREAD_PRIORITY_SUPPORT) +#error "Your platform must define SB_HAS_THREAD_PRIORITY_SUPPORT." +#endif + #if !defined(SB_PREFERRED_RGBA_BYTE_ORDER) // Legal values for SB_PREFERRED_RGBA_BYTE_ORDER are defined in this file above // as SB_PREFERRED_RGBA_BYTE_ORDER_*. @@ -416,6 +457,15 @@ #define SB_HAS_GLES2 !SB_GYP_GL_TYPE_IS_NONE #endif +// Specifies whether this platform has any kind of supported graphics system. +#if !defined(SB_HAS_GRAPHICS) +#if SB_HAS(GLES2) || SB_HAS(BLITTER) +#define SB_HAS_GRAPHICS 1 +#else +#define SB_HAS_GRAPHICS 0 +#endif +#endif + // Specifies whether the starboard media pipeline components (SbPlayerPipeline // and StarboardDecryptor) are used. Set to 0 means they are not used. #define SB_CAN_MEDIA_USE_STARBOARD_PIPELINE \
diff --git a/src/starboard/creator/ci20directfb/README.md b/src/starboard/creator/ci20directfb/README.md new file mode 100644 index 0000000..e0c1251 --- /dev/null +++ b/src/starboard/creator/ci20directfb/README.md
@@ -0,0 +1,4 @@ +# Setting up Starboard to use DirectFB on a Creator Ci20 + +See `starboard/raspi/directfb/README.md`. The steps for getting directfb +running on the Ci20 are identical.
diff --git a/src/starboard/creator/ci20directfb/atomic_public.h b/src/starboard/creator/ci20directfb/atomic_public.h new file mode 100644 index 0000000..2a182e7 --- /dev/null +++ b/src/starboard/creator/ci20directfb/atomic_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_CREATOR_CI20DIRECTFB_ATOMIC_PUBLIC_H_ +#define STARBOARD_CREATOR_CI20DIRECTFB_ATOMIC_PUBLIC_H_ + +#include "starboard/linux/shared/atomic_public.h" + +#endif // STARBOARD_CREATOR_CI20DIRECTFB_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/creator/ci20directfb/configuration_public.h b/src/starboard/creator/ci20directfb/configuration_public.h new file mode 100644 index 0000000..651c203 --- /dev/null +++ b/src/starboard/creator/ci20directfb/configuration_public.h
@@ -0,0 +1,46 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_CREATOR_CI20DIRECTFB_CONFIGURATION_PUBLIC_H_ +#define STARBOARD_CREATOR_CI20DIRECTFB_CONFIGURATION_PUBLIC_H_ + +#include "starboard/creator/shared/configuration_public.h" + +// Indicates whether or not the given platform supports rendering of NV12 +// textures. These textures typically originate from video decoders. +#undef SB_HAS_NV12_TEXTURE_SUPPORT +#define SB_HAS_NV12_TEXTURE_SUPPORT 0 + +// This configuration supports the blitter API (implemented via DirectFB). +#undef SB_HAS_BLITTER +#define SB_HAS_BLITTER 1 + +// Unfortunately, DirectFB does not support bilinear filtering. According to +// http://osdir.com/ml/graphics.directfb.user/2008-06/msg00028.html, "smooth +// scaling is not supported in conjunction with blending", and we need blending +// more. +#undef SB_HAS_BILINEAR_FILTERING_SUPPORT +#define SB_HAS_BILINEAR_FILTERING_SUPPORT 0 + +// DirectFB's only 32-bit RGBA color format is word-order ARGB. This translates +// to byte-order ARGB for big endian platforms and byte-order BGRA for +// little-endian platforms. +#undef SB_PREFERRED_RGBA_BYTE_ORDER +#if SB_IS(BIG_ENDIAN) +#define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_ARGB +#else +#define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_BGRA +#endif + +#endif // STARBOARD_CREATOR_CI20DIRECTFB_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/creator/ci20directfb/gyp_configuration.gypi b/src/starboard/creator/ci20directfb/gyp_configuration.gypi new file mode 100644 index 0000000..8d8c263 --- /dev/null +++ b/src/starboard/creator/ci20directfb/gyp_configuration.gypi
@@ -0,0 +1,45 @@ +# Copyright 2016 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. + +{ + 'variables': { + 'platform_libraries': [ + '-ldirectfb', + '-ldirect', + ], + 'gl_type': 'none', + }, + + 'target_defaults': { + 'default_configuration': 'creator-ci20directfb_debug', + 'configurations': { + 'creator-ci20directfb_debug': { + 'inherit_from': ['debug_base'], + }, + 'creator-ci20directfb_devel': { + 'inherit_from': ['devel_base'], + }, + 'creator-ci20directfb_qa': { + 'inherit_from': ['qa_base'], + }, + 'creator-ci20directfb_gold': { + 'inherit_from': ['gold_base'], + }, + }, # end of configurations + }, + + 'includes': [ + '../shared/gyp_configuration.gypi', + ], +}
diff --git a/src/starboard/creator/ci20directfb/gyp_configuration.py b/src/starboard/creator/ci20directfb/gyp_configuration.py new file mode 100644 index 0000000..4e712f5 --- /dev/null +++ b/src/starboard/creator/ci20directfb/gyp_configuration.py
@@ -0,0 +1,32 @@ +# Copyright 2016 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. +"""Starboard Creator Ci20 DirectFB platform configuration for gyp_cobalt.""" + +import logging +import os +import sys + +# Import the shared Creator platform configuration. +sys.path.append( + os.path.realpath( + os.path.join(os.path.dirname(__file__), os.pardir, 'shared'))) +import gyp_configuration # pylint: disable=import-self,g-import-not-at-top + + +def CreatePlatformConfig(): + try: + return gyp_configuration.PlatformConfig('creator-ci20directfb') + except RuntimeError as e: + logging.critical(e) + return None
diff --git a/src/starboard/creator/ci20directfb/main.cc b/src/starboard/creator/ci20directfb/main.cc new file mode 100644 index 0000000..68d7761 --- /dev/null +++ b/src/starboard/creator/ci20directfb/main.cc
@@ -0,0 +1,29 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/configuration.h" +#include "starboard/shared/directfb/application_directfb.h" +#include "starboard/shared/signal/crash_signals.h" +#include "starboard/shared/signal/suspend_signals.h" + +int main(int argc, char** argv) { + tzset(); + starboard::shared::signal::InstallCrashSignalHandlers(); + starboard::shared::signal::InstallSuspendSignalHandlers(); + starboard::ApplicationDirectFB application; + int result = application.Run(argc, argv); + starboard::shared::signal::UninstallSuspendSignalHandlers(); + starboard::shared::signal::UninstallCrashSignalHandlers(); + return result; +}
diff --git a/src/starboard/creator/ci20directfb/starboard_platform.gyp b/src/starboard/creator/ci20directfb/starboard_platform.gyp new file mode 100644 index 0000000..284be6b --- /dev/null +++ b/src/starboard/creator/ci20directfb/starboard_platform.gyp
@@ -0,0 +1,335 @@ +# Copyright 2016 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. +{ + 'targets': [ + { + 'target_name': 'starboard_base_symbolize', + 'type': 'static_library', + 'sources': [ + '<(DEPTH)/base/third_party/symbolize/demangle.cc', + '<(DEPTH)/base/third_party/symbolize/symbolize.cc', + ], + }, + { + 'target_name': 'starboard_platform', + 'type': 'static_library', + 'sources': [ + '<(DEPTH)/starboard/creator/ci20directfb/configuration_public.h', + '<(DEPTH)/starboard/creator/ci20directfb/main.cc', + '<(DEPTH)/starboard/creator/ci20directfb/system_get_property.cc', + '<(DEPTH)/starboard/linux/shared/atomic_public.h', + '<(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', + '<(DEPTH)/starboard/linux/shared/system_has_capability.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h', + '<(DEPTH)/starboard/shared/alsa/alsa_util.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_util.h', + '<(DEPTH)/starboard/shared/alsa/audio_sink_get_max_channels.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_get_nearest_supported_sample_frequency.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_frame_storage_type_supported.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_sample_type_supported.cc', + '<(DEPTH)/starboard/shared/directfb/application_directfb.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_blit_rect_to_rect.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_context.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_default_device.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_pixel_data.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_render_target_surface.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_surface_from_pixel_data.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_swap_chain_from_window.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_context.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_device.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_pixel_data.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_surface.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_swap_chain.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_download_surface_pixels.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_fill_rect.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_flip_swap_chain.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_flush_context.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_max_contexts.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_pixel_data_pitch_in_bytes.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_pixel_data_pointer.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_render_target_from_surface.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_render_target_from_swap_chain.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_surface_info.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_internal.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_is_pixel_format_supported_by_download_surface_pixels.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_is_pixel_format_supported_by_pixel_data.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_is_surface_format_supported_by_render_target_surface.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_blending.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_color.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_modulate_blits_with_color.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_render_target.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_scissor.cc', + '<(DEPTH)/starboard/shared/directfb/window_create.cc', + '<(DEPTH)/starboard/shared/directfb/window_destroy.cc', + '<(DEPTH)/starboard/shared/directfb/window_get_platform_handle.cc', + '<(DEPTH)/starboard/shared/directfb/window_get_size.cc', + '<(DEPTH)/starboard/shared/directfb/window_internal.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_aligned_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_free.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h', + '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h', + '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc', + '<(DEPTH)/starboard/shared/iso/character_is_digit.cc', + '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc', + '<(DEPTH)/starboard/shared/iso/character_is_space.cc', + '<(DEPTH)/starboard/shared/iso/character_is_upper.cc', + '<(DEPTH)/starboard/shared/iso/character_to_lower.cc', + '<(DEPTH)/starboard/shared/iso/character_to_upper.cc', + '<(DEPTH)/starboard/shared/iso/directory_close.cc', + '<(DEPTH)/starboard/shared/iso/directory_get_next.cc', + '<(DEPTH)/starboard/shared/iso/directory_open.cc', + '<(DEPTH)/starboard/shared/iso/double_absolute.cc', + '<(DEPTH)/starboard/shared/iso/double_exponent.cc', + '<(DEPTH)/starboard/shared/iso/double_floor.cc', + '<(DEPTH)/starboard/shared/iso/double_is_finite.cc', + '<(DEPTH)/starboard/shared/iso/double_is_nan.cc', + '<(DEPTH)/starboard/shared/iso/memory_compare.cc', + '<(DEPTH)/starboard/shared/iso/memory_copy.cc', + '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc', + '<(DEPTH)/starboard/shared/iso/memory_move.cc', + '<(DEPTH)/starboard/shared/iso/memory_set.cc', + '<(DEPTH)/starboard/shared/iso/string_compare.cc', + '<(DEPTH)/starboard/shared/iso/string_compare_all.cc', + '<(DEPTH)/starboard/shared/iso/string_find_character.cc', + '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc', + '<(DEPTH)/starboard/shared/iso/string_find_string.cc', + '<(DEPTH)/starboard/shared/iso/string_get_length.cc', + '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_double.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc', + '<(DEPTH)/starboard/shared/iso/string_scan.cc', + '<(DEPTH)/starboard/shared/iso/system_binary_search.cc', + '<(DEPTH)/starboard/shared/iso/system_sort.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc', + '<(DEPTH)/starboard/shared/linux/byte_swap.cc', + '<(DEPTH)/starboard/shared/linux/get_home_directory.cc', + '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc', + '<(DEPTH)/starboard/shared/linux/page_internal.cc', + '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc', + '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc', + '<(DEPTH)/starboard/shared/linux/system_get_stack.cc', + '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc', + '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc', + '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc', + '<(DEPTH)/starboard/shared/linux/system_symbolize.cc', + '<(DEPTH)/starboard/shared/linux/thread_get_id.cc', + '<(DEPTH)/starboard/shared/linux/thread_get_name.cc', + '<(DEPTH)/starboard/shared/linux/thread_set_name.cc', + '<(DEPTH)/starboard/shared/nouser/user_get_current.cc', + '<(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/posix/directory_create.cc', + '<(DEPTH)/starboard/shared/posix/file_can_open.cc', + '<(DEPTH)/starboard/shared/posix/file_close.cc', + '<(DEPTH)/starboard/shared/posix/file_delete.cc', + '<(DEPTH)/starboard/shared/posix/file_exists.cc', + '<(DEPTH)/starboard/shared/posix/file_flush.cc', + '<(DEPTH)/starboard/shared/posix/file_get_info.cc', + '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc', + '<(DEPTH)/starboard/shared/posix/file_open.cc', + '<(DEPTH)/starboard/shared/posix/file_read.cc', + '<(DEPTH)/starboard/shared/posix/file_seek.cc', + '<(DEPTH)/starboard/shared/posix/file_truncate.cc', + '<(DEPTH)/starboard/shared/posix/file_write.cc', + '<(DEPTH)/starboard/shared/posix/log.cc', + '<(DEPTH)/starboard/shared/posix/log_flush.cc', + '<(DEPTH)/starboard/shared/posix/log_format.cc', + '<(DEPTH)/starboard/shared/posix/log_is_tty.cc', + '<(DEPTH)/starboard/shared/posix/log_raw.cc', + '<(DEPTH)/starboard/shared/posix/memory_flush.cc', + '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc', + '<(DEPTH)/starboard/shared/posix/socket_accept.cc', + '<(DEPTH)/starboard/shared/posix/socket_bind.cc', + '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc', + '<(DEPTH)/starboard/shared/posix/socket_connect.cc', + '<(DEPTH)/starboard/shared/posix/socket_create.cc', + '<(DEPTH)/starboard/shared/posix/socket_destroy.cc', + '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc', + '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc', + '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc', + '<(DEPTH)/starboard/shared/posix/socket_internal.cc', + '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc', + '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc', + '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc', + '<(DEPTH)/starboard/shared/posix/socket_listen.cc', + '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc', + '<(DEPTH)/starboard/shared/posix/socket_resolve.cc', + '<(DEPTH)/starboard/shared/posix/socket_send_to.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc', + '<(DEPTH)/starboard/shared/posix/string_format.cc', + '<(DEPTH)/starboard/shared/posix/string_format_wide.cc', + '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc', + '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc', + '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc', + '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc', + '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc', + '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc', + '<(DEPTH)/starboard/shared/posix/thread_sleep.cc', + '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc', + '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc', + '<(DEPTH)/starboard/shared/posix/time_get_now.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_create.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_release.cc', + '<(DEPTH)/starboard/shared/pthread/once.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc', + '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc', + '<(DEPTH)/starboard/shared/pthread/thread_detach.cc', + '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc', + '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc', + '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc', + '<(DEPTH)/starboard/shared/pthread/thread_join.cc', + '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc', + '<(DEPTH)/starboard/shared/pthread/thread_yield.cc', + '<(DEPTH)/starboard/shared/signal/crash_signals.h', + '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc', + '<(DEPTH)/starboard/shared/signal/suspend_signals.cc', + '<(DEPTH)/starboard/shared/signal/suspend_signals.h', + '<(DEPTH)/starboard/shared/starboard/application.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_is_valid.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.h', + '<(DEPTH)/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc', + '<(DEPTH)/starboard/shared/starboard/blitter_blit_rects_to_rects.cc', + '<(DEPTH)/starboard/shared/starboard/directory_can_open.cc', + '<(DEPTH)/starboard/shared/starboard/event_cancel.cc', + '<(DEPTH)/starboard/shared/starboard/event_schedule.cc', + '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc', + '<(DEPTH)/starboard/shared/starboard/log_message.cc', + '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc', + '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc', + '<(DEPTH)/starboard/shared/starboard/media/mime_parser.cc', + '<(DEPTH)/starboard/shared/starboard/media/mime_parser.h', + '<(DEPTH)/starboard/shared/starboard/new.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/player_create.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_worker.h', + '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc', + '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h', + '<(DEPTH)/starboard/shared/starboard/queue_application.cc', + '<(DEPTH)/starboard/shared/starboard/string_concat.cc', + '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc', + '<(DEPTH)/starboard/shared/starboard/string_copy.cc', + '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc', + '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc', + '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc', + '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc', + '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc', + '<(DEPTH)/starboard/shared/stub/drm_close_session.cc', + '<(DEPTH)/starboard/shared/stub/drm_create_system.cc', + '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc', + '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc', + '<(DEPTH)/starboard/shared/stub/drm_system_internal.h', + '<(DEPTH)/starboard/shared/stub/drm_update_session.cc', + '<(DEPTH)/starboard/shared/stub/media_is_supported.cc', + '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc', + '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc', + '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc', + '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc', + '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc', + ], + 'include_dirs': [ + '<(sysroot)/mipsel-r2-hard/usr/include/directfb', + ], + 'defines': [ + # This must be defined when building Starboard, and must not when + # building Starboard client code. + 'STARBOARD_IMPLEMENTATION', + ], + 'dependencies': [ + '<(DEPTH)/starboard/common/common.gyp:common', + '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc', + '<(DEPTH)/third_party/libevent/libevent.gyp:libevent', + 'starboard_base_symbolize', + ], + }, + ], +}
diff --git a/src/starboard/creator/ci20directfb/system_get_property.cc b/src/starboard/creator/ci20directfb/system_get_property.cc new file mode 100644 index 0000000..51ca55f --- /dev/null +++ b/src/starboard/creator/ci20directfb/system_get_property.cc
@@ -0,0 +1,70 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/system.h" + +#include "starboard/log.h" +#include "starboard/string.h" + +namespace { + +const char* kFriendlyName = "Creator Ci20"; +const char* kPlatformName = "DirectFB; Creator Ci20 JZ4780"; + +bool CopyStringAndTestIfSuccess(char* out_value, + int value_length, + const char* from_value) { + if (SbStringGetLength(from_value) + 1 > value_length) + return false; + SbStringCopy(out_value, from_value, value_length); + return true; +} + +} // namespace + +bool SbSystemGetProperty(SbSystemPropertyId property_id, + char* out_value, + int value_length) { + if (!out_value || !value_length) { + return false; + } + + switch (property_id) { + case kSbSystemPropertyBrandName: + case kSbSystemPropertyChipsetModelNumber: + case kSbSystemPropertyFirmwareVersion: + case kSbSystemPropertyModelName: + case kSbSystemPropertyModelYear: + case kSbSystemPropertyNetworkOperatorName: + case kSbSystemPropertySpeechApiKey: + return false; + + case kSbSystemPropertyFriendlyName: + return CopyStringAndTestIfSuccess(out_value, value_length, kFriendlyName); + + case kSbSystemPropertyPlatformName: + return CopyStringAndTestIfSuccess(out_value, value_length, kPlatformName); + + case kSbSystemPropertyPlatformUuid: + SB_NOTIMPLEMENTED(); + return CopyStringAndTestIfSuccess(out_value, value_length, "N/A"); + + default: + SB_DLOG(WARNING) << __FUNCTION__ + << ": Unrecognized property: " << property_id; + break; + } + + return false; +}
diff --git a/src/starboard/creator/ci20directfb/thread_types_public.h b/src/starboard/creator/ci20directfb/thread_types_public.h new file mode 100644 index 0000000..b531032 --- /dev/null +++ b/src/starboard/creator/ci20directfb/thread_types_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_CREATOR_CI20DIRECTFB_THREAD_TYPES_PUBLIC_H_ +#define STARBOARD_CREATOR_CI20DIRECTFB_THREAD_TYPES_PUBLIC_H_ + +#include "starboard/linux/shared/thread_types_public.h" + +#endif // STARBOARD_CREATOR_CI20DIRECTFB_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/creator/ci20x11/atomic_public.h b/src/starboard/creator/ci20x11/atomic_public.h new file mode 100644 index 0000000..932fe9d --- /dev/null +++ b/src/starboard/creator/ci20x11/atomic_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_CREATOR_CI20X11_ATOMIC_PUBLIC_H_ +#define STARBOARD_CREATOR_CI20X11_ATOMIC_PUBLIC_H_ + +#include "starboard/linux/shared/atomic_public.h" + +#endif // STARBOARD_CREATOR_CI20X11_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/creator/ci20x11/configuration_public.h b/src/starboard/creator/ci20x11/configuration_public.h new file mode 100644 index 0000000..4c00bad --- /dev/null +++ b/src/starboard/creator/ci20x11/configuration_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_CREATOR_CI20X11_CONFIGURATION_PUBLIC_H_ +#define STARBOARD_CREATOR_CI20X11_CONFIGURATION_PUBLIC_H_ + +#include "starboard/creator/shared/configuration_public.h" + +#endif // STARBOARD_CREATOR_CI20X11_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/creator/ci20x11/gyp_configuration.gypi b/src/starboard/creator/ci20x11/gyp_configuration.gypi new file mode 100644 index 0000000..a2ea1b8 --- /dev/null +++ b/src/starboard/creator/ci20x11/gyp_configuration.gypi
@@ -0,0 +1,48 @@ +# Copyright 2016 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. + +{ + 'variables': { + 'platform_libraries': [ + '-lEGL', + '-lGLESv2', + '-lX11', + '-lXcomposite', + '-lXext', + '-lXrender', + ], + }, + + 'target_defaults': { + 'default_configuration': 'creator-ci20x11_debug', + 'configurations': { + 'creator-ci20x11_debug': { + 'inherit_from': ['debug_base'], + }, + 'creator-ci20x11_devel': { + 'inherit_from': ['devel_base'], + }, + 'creator-ci20x11_qa': { + 'inherit_from': ['qa_base'], + }, + 'creator-ci20x11_gold': { + 'inherit_from': ['gold_base'], + }, + }, # end of configurations + }, + + 'includes': [ + '../shared/gyp_configuration.gypi', + ], +}
diff --git a/src/starboard/creator/ci20x11/gyp_configuration.py b/src/starboard/creator/ci20x11/gyp_configuration.py new file mode 100644 index 0000000..1c062df --- /dev/null +++ b/src/starboard/creator/ci20x11/gyp_configuration.py
@@ -0,0 +1,32 @@ +# Copyright 2016 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. +"""Starboard Creator Ci20 X11 platform configuration for gyp_cobalt.""" + +import logging +import os +import sys + +# Import the shared Creator platform configuration. +sys.path.append( + os.path.realpath( + os.path.join(os.path.dirname(__file__), os.pardir, 'shared'))) +import gyp_configuration # pylint: disable=import-self,g-import-not-at-top + + +def CreatePlatformConfig(): + try: + return gyp_configuration.PlatformConfig('creator-ci20x11') + except RuntimeError as e: + logging.critical(e) + return None
diff --git a/src/starboard/creator/ci20x11/main.cc b/src/starboard/creator/ci20x11/main.cc new file mode 100644 index 0000000..7086895 --- /dev/null +++ b/src/starboard/creator/ci20x11/main.cc
@@ -0,0 +1,29 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/configuration.h" +#include "starboard/shared/signal/crash_signals.h" +#include "starboard/shared/signal/suspend_signals.h" +#include "starboard/shared/x11/application_x11.h" + +extern "C" SB_EXPORT_PLATFORM int main(int argc, char** argv) { + tzset(); + starboard::shared::signal::InstallCrashSignalHandlers(); + starboard::shared::signal::InstallSuspendSignalHandlers(); + starboard::shared::x11::ApplicationX11 application; + int result = application.Run(argc, argv); + starboard::shared::signal::UninstallSuspendSignalHandlers(); + starboard::shared::signal::UninstallCrashSignalHandlers(); + return result; +}
diff --git a/src/starboard/creator/ci20x11/starboard_platform.gyp b/src/starboard/creator/ci20x11/starboard_platform.gyp new file mode 100644 index 0000000..d061e46 --- /dev/null +++ b/src/starboard/creator/ci20x11/starboard_platform.gyp
@@ -0,0 +1,301 @@ +# Copyright 2016 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. +{ + 'targets': [ + { + 'target_name': 'starboard_base_symbolize', + 'type': 'static_library', + 'sources': [ + '<(DEPTH)/base/third_party/symbolize/demangle.cc', + '<(DEPTH)/base/third_party/symbolize/symbolize.cc', + ], + }, + { + 'target_name': 'starboard_platform', + 'type': 'static_library', + 'sources': [ + '<(DEPTH)/starboard/creator/ci20x11/atomic_public.h', + '<(DEPTH)/starboard/creator/ci20x11/configuration_public.h', + '<(DEPTH)/starboard/creator/ci20x11/main.cc', + '<(DEPTH)/starboard/creator/ci20x11/system_get_property.cc', + '<(DEPTH)/starboard/linux/shared/atomic_public.h', + '<(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', + '<(DEPTH)/starboard/linux/shared/system_has_capability.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h', + '<(DEPTH)/starboard/shared/alsa/alsa_util.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_util.h', + '<(DEPTH)/starboard/shared/alsa/audio_sink_get_max_channels.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_get_nearest_supported_sample_frequency.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_frame_storage_type_supported.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_sample_type_supported.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_aligned_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_free.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h', + '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h', + '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc', + '<(DEPTH)/starboard/shared/iso/character_is_digit.cc', + '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc', + '<(DEPTH)/starboard/shared/iso/character_is_space.cc', + '<(DEPTH)/starboard/shared/iso/character_is_upper.cc', + '<(DEPTH)/starboard/shared/iso/character_to_lower.cc', + '<(DEPTH)/starboard/shared/iso/character_to_upper.cc', + '<(DEPTH)/starboard/shared/iso/directory_close.cc', + '<(DEPTH)/starboard/shared/iso/directory_get_next.cc', + '<(DEPTH)/starboard/shared/iso/directory_open.cc', + '<(DEPTH)/starboard/shared/iso/double_absolute.cc', + '<(DEPTH)/starboard/shared/iso/double_exponent.cc', + '<(DEPTH)/starboard/shared/iso/double_floor.cc', + '<(DEPTH)/starboard/shared/iso/double_is_finite.cc', + '<(DEPTH)/starboard/shared/iso/double_is_nan.cc', + '<(DEPTH)/starboard/shared/iso/memory_compare.cc', + '<(DEPTH)/starboard/shared/iso/memory_copy.cc', + '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc', + '<(DEPTH)/starboard/shared/iso/memory_move.cc', + '<(DEPTH)/starboard/shared/iso/memory_set.cc', + '<(DEPTH)/starboard/shared/iso/string_compare.cc', + '<(DEPTH)/starboard/shared/iso/string_compare_all.cc', + '<(DEPTH)/starboard/shared/iso/string_find_character.cc', + '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc', + '<(DEPTH)/starboard/shared/iso/string_find_string.cc', + '<(DEPTH)/starboard/shared/iso/string_get_length.cc', + '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_double.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc', + '<(DEPTH)/starboard/shared/iso/string_scan.cc', + '<(DEPTH)/starboard/shared/iso/system_binary_search.cc', + '<(DEPTH)/starboard/shared/iso/system_sort.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc', + '<(DEPTH)/starboard/shared/linux/byte_swap.cc', + '<(DEPTH)/starboard/shared/linux/get_home_directory.cc', + '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc', + '<(DEPTH)/starboard/shared/linux/page_internal.cc', + '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc', + '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc', + '<(DEPTH)/starboard/shared/linux/system_get_stack.cc', + '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc', + '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc', + '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc', + '<(DEPTH)/starboard/shared/linux/system_symbolize.cc', + '<(DEPTH)/starboard/shared/linux/thread_get_id.cc', + '<(DEPTH)/starboard/shared/linux/thread_get_name.cc', + '<(DEPTH)/starboard/shared/linux/thread_set_name.cc', + '<(DEPTH)/starboard/shared/nouser/user_get_current.cc', + '<(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/posix/directory_create.cc', + '<(DEPTH)/starboard/shared/posix/file_can_open.cc', + '<(DEPTH)/starboard/shared/posix/file_close.cc', + '<(DEPTH)/starboard/shared/posix/file_delete.cc', + '<(DEPTH)/starboard/shared/posix/file_exists.cc', + '<(DEPTH)/starboard/shared/posix/file_flush.cc', + '<(DEPTH)/starboard/shared/posix/file_get_info.cc', + '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc', + '<(DEPTH)/starboard/shared/posix/file_open.cc', + '<(DEPTH)/starboard/shared/posix/file_read.cc', + '<(DEPTH)/starboard/shared/posix/file_seek.cc', + '<(DEPTH)/starboard/shared/posix/file_truncate.cc', + '<(DEPTH)/starboard/shared/posix/file_write.cc', + '<(DEPTH)/starboard/shared/posix/log.cc', + '<(DEPTH)/starboard/shared/posix/log_flush.cc', + '<(DEPTH)/starboard/shared/posix/log_format.cc', + '<(DEPTH)/starboard/shared/posix/log_is_tty.cc', + '<(DEPTH)/starboard/shared/posix/log_raw.cc', + '<(DEPTH)/starboard/shared/posix/memory_flush.cc', + '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc', + '<(DEPTH)/starboard/shared/posix/socket_accept.cc', + '<(DEPTH)/starboard/shared/posix/socket_bind.cc', + '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc', + '<(DEPTH)/starboard/shared/posix/socket_connect.cc', + '<(DEPTH)/starboard/shared/posix/socket_create.cc', + '<(DEPTH)/starboard/shared/posix/socket_destroy.cc', + '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc', + '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc', + '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc', + '<(DEPTH)/starboard/shared/posix/socket_internal.cc', + '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc', + '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc', + '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc', + '<(DEPTH)/starboard/shared/posix/socket_listen.cc', + '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc', + '<(DEPTH)/starboard/shared/posix/socket_resolve.cc', + '<(DEPTH)/starboard/shared/posix/socket_send_to.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc', + '<(DEPTH)/starboard/shared/posix/string_format.cc', + '<(DEPTH)/starboard/shared/posix/string_format_wide.cc', + '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc', + '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc', + '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc', + '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc', + '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc', + '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc', + '<(DEPTH)/starboard/shared/posix/thread_sleep.cc', + '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc', + '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc', + '<(DEPTH)/starboard/shared/posix/time_get_now.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_create.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_release.cc', + '<(DEPTH)/starboard/shared/pthread/once.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create_priority.h', + '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc', + '<(DEPTH)/starboard/shared/pthread/thread_detach.cc', + '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc', + '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc', + '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc', + '<(DEPTH)/starboard/shared/pthread/thread_join.cc', + '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc', + '<(DEPTH)/starboard/shared/pthread/thread_yield.cc', + '<(DEPTH)/starboard/shared/signal/crash_signals.h', + '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc', + '<(DEPTH)/starboard/shared/signal/suspend_signals.cc', + '<(DEPTH)/starboard/shared/signal/suspend_signals.h', + '<(DEPTH)/starboard/shared/starboard/application.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_is_valid.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.h', + '<(DEPTH)/starboard/shared/starboard/directory_can_open.cc', + '<(DEPTH)/starboard/shared/starboard/event_cancel.cc', + '<(DEPTH)/starboard/shared/starboard/event_schedule.cc', + '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc', + '<(DEPTH)/starboard/shared/starboard/log_message.cc', + '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc', + '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc', + '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc', + '<(DEPTH)/starboard/shared/starboard/media/mime_type.h', + '<(DEPTH)/starboard/shared/starboard/new.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/player_create.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_worker.h', + '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc', + '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h', + '<(DEPTH)/starboard/shared/starboard/queue_application.cc', + '<(DEPTH)/starboard/shared/starboard/string_concat.cc', + '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc', + '<(DEPTH)/starboard/shared/starboard/string_copy.cc', + '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc', + '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc', + '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc', + '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc', + '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc', + '<(DEPTH)/starboard/shared/stub/drm_close_session.cc', + '<(DEPTH)/starboard/shared/stub/drm_create_system.cc', + '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc', + '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc', + '<(DEPTH)/starboard/shared/stub/drm_system_internal.h', + '<(DEPTH)/starboard/shared/stub/drm_update_session.cc', + '<(DEPTH)/starboard/shared/stub/media_is_supported.cc', + '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc', + '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc', + '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc', + '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc', + '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc', + '<(DEPTH)/starboard/shared/x11/application_x11.cc', + '<(DEPTH)/starboard/shared/x11/window_create.cc', + '<(DEPTH)/starboard/shared/x11/window_destroy.cc', + '<(DEPTH)/starboard/shared/x11/window_get_platform_handle.cc', + '<(DEPTH)/starboard/shared/x11/window_get_size.cc', + '<(DEPTH)/starboard/shared/x11/window_internal.cc', + ], + 'defines': [ + # This must be defined when building Starboard, and must not when + # building Starboard client code. + 'STARBOARD_IMPLEMENTATION', + ], + 'dependencies': [ + '<(DEPTH)/starboard/common/common.gyp:common', + '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc', + '<(DEPTH)/third_party/libevent/libevent.gyp:libevent', + 'starboard_base_symbolize', + ], + }, + ], +}
diff --git a/src/starboard/creator/ci20x11/system_get_property.cc b/src/starboard/creator/ci20x11/system_get_property.cc new file mode 100644 index 0000000..9d71165 --- /dev/null +++ b/src/starboard/creator/ci20x11/system_get_property.cc
@@ -0,0 +1,70 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/system.h" + +#include "starboard/log.h" +#include "starboard/string.h" + +namespace { + +const char* kFriendlyName = "Creator Ci20"; +const char* kPlatformName = "Creator Ci20 JZ4780"; + +bool CopyStringAndTestIfSuccess(char* out_value, + int value_length, + const char* from_value) { + if (SbStringGetLength(from_value) + 1 > value_length) + return false; + SbStringCopy(out_value, from_value, value_length); + return true; +} + +} // namespace + +bool SbSystemGetProperty(SbSystemPropertyId property_id, + char* out_value, + int value_length) { + if (!out_value || !value_length) { + return false; + } + + switch (property_id) { + case kSbSystemPropertyBrandName: + case kSbSystemPropertyChipsetModelNumber: + case kSbSystemPropertyFirmwareVersion: + case kSbSystemPropertyModelName: + case kSbSystemPropertyModelYear: + case kSbSystemPropertyNetworkOperatorName: + case kSbSystemPropertySpeechApiKey: + return false; + + case kSbSystemPropertyFriendlyName: + return CopyStringAndTestIfSuccess(out_value, value_length, kFriendlyName); + + case kSbSystemPropertyPlatformName: + return CopyStringAndTestIfSuccess(out_value, value_length, kPlatformName); + + case kSbSystemPropertyPlatformUuid: + SB_NOTIMPLEMENTED(); + return CopyStringAndTestIfSuccess(out_value, value_length, "N/A"); + + default: + SB_DLOG(WARNING) << __FUNCTION__ + << ": Unrecognized property: " << property_id; + break; + } + + return false; +}
diff --git a/src/starboard/creator/ci20x11/thread_types_public.h b/src/starboard/creator/ci20x11/thread_types_public.h new file mode 100644 index 0000000..7437a70 --- /dev/null +++ b/src/starboard/creator/ci20x11/thread_types_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_CREATOR_CI20X11_THREAD_TYPES_PUBLIC_H_ +#define STARBOARD_CREATOR_CI20X11_THREAD_TYPES_PUBLIC_H_ + +#include "starboard/linux/shared/thread_types_public.h" + +#endif // STARBOARD_CREATOR_CI20X11_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/creator/shared/configuration_public.h b/src/starboard/creator/shared/configuration_public.h new file mode 100644 index 0000000..4e572ea --- /dev/null +++ b/src/starboard/creator/shared/configuration_public.h
@@ -0,0 +1,460 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The shared Starboard configuration for Creator devices. + +#ifndef STARBOARD_CREATOR_SHARED_CONFIGURATION_PUBLIC_H_ +#define STARBOARD_CREATOR_SHARED_CONFIGURATION_PUBLIC_H_ + +// --- Architecture Configuration -------------------------------------------- + +// Whether the current platform is big endian. SB_IS_LITTLE_ENDIAN will be +// automatically set based on this. +#define SB_IS_BIG_ENDIAN 0 + +// Whether the current platform is an ARM architecture. +#define SB_IS_ARCH_ARM 0 + +// Whether the current platform is a MIPS architecture. +#define SB_IS_ARCH_MIPS 1 + +// Whether the current platform is a PPC architecture. +#define SB_IS_ARCH_PPC 0 + +// Whether the current platform is an x86 architecture. +#define SB_IS_ARCH_X86 0 + +// Whether the current platform is a 32-bit architecture. +#define SB_IS_32_BIT 1 + +// Whether the current platform is a 64-bit architecture. +#define SB_IS_64_BIT 0 + +// Whether the current platform's pointers are 32-bit. +// Whether the current platform's longs are 32-bit. +#if SB_IS(32_BIT) +#define SB_HAS_32_BIT_POINTERS 1 +#define SB_HAS_32_BIT_LONG 1 +#else +#define SB_HAS_32_BIT_POINTERS 0 +#define SB_HAS_32_BIT_LONG 0 +#endif + +// Whether the current platform's pointers are 64-bit. +// Whether the current platform's longs are 64-bit. +#if SB_IS(64_BIT) +#define SB_HAS_64_BIT_POINTERS 1 +#define SB_HAS_64_BIT_LONG 1 +#else +#define SB_HAS_64_BIT_POINTERS 0 +#define SB_HAS_64_BIT_LONG 0 +#endif + +// Configuration parameters that allow the application to make some general +// compile-time decisions with respect to the the number of cores likely to be +// available on this platform. For a definitive measure, the application should +// still call SbSystemGetNumberOfProcessors at runtime. + +// Whether the current platform is expected to have many cores (> 6), or a +// wildly varying number of cores. +#define SB_HAS_MANY_CORES 0 + +// Whether the current platform is expected to have exactly 1 core. +#define SB_HAS_1_CORE 0 + +// Whether the current platform is expected to have exactly 2 cores. +#define SB_HAS_2_CORES 1 + +// Whether the current platform is expected to have exactly 4 cores. +#define SB_HAS_4_CORES 0 + +// Whether the current platform is expected to have exactly 6 cores. +#define SB_HAS_6_CORES 0 + +// Whether the current platform's thread scheduler will automatically balance +// threads between cores, as opposed to systems where threads will only ever run +// on the specifically pinned core. +#define SB_HAS_CROSS_CORE_SCHEDULER 1 + +// The API version implemented by this platform. +#define SB_API_VERSION 2 + +// --- System Header Configuration ------------------------------------------- + +// Any system headers listed here that are not provided by the platform will be +// emulated in starboard/types.h. + +// Whether the current platform provides the standard header stdarg.h. +#define SB_HAS_STDARG_H 1 + +// Whether the current platform provides the standard header stdbool.h. +#define SB_HAS_STDBOOL_H 1 + +// Whether the current platform provides the standard header stddef.h. +#define SB_HAS_STDDEF_H 1 + +// Whether the current platform provides the standard header stdint.h. +#define SB_HAS_STDINT_H 1 + +// Whether the current platform provides the standard header inttypes.h. +#define SB_HAS_INTTYPES_H 1 + +// Whether the current platform provides the standard header wchar.h. +#define SB_HAS_WCHAR_H 1 + +// Whether the current platform provides the standard header limits.h. +#define SB_HAS_LIMITS_H 1 + +// Whether the current platform provides the standard header float.h. +#define SB_HAS_FLOAT_H 1 + +// Whether the current platform has microphone supported. +#define SB_HAS_MICROPHONE 0 + +// Whether the current platform has speech synthesis. +#define SB_HAS_SPEECH_SYNTHESIS 0 + +// Type detection for wchar_t. +#if defined(__WCHAR_MAX__) && \ + (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff) +#define SB_IS_WCHAR_T_UTF32 1 +#elif defined(__WCHAR_MAX__) && \ + (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff) +#define SB_IS_WCHAR_T_UTF16 1 +#endif + +// Chrome only defines these two if ARMEL or MIPSEL are defined. +#if defined(__ARMEL__) +// Chrome has an exclusion for iOS here, we should too when we support iOS. +#define SB_IS_WCHAR_T_UNSIGNED 1 +#elif defined(__MIPSEL__) +#define SB_IS_WCHAR_T_SIGNED 1 +#endif + +// --- Architecture Configuration -------------------------------------------- + +// On default Linux, you must be a superuser in order to set real time +// scheduling on threads. +#define SB_HAS_THREAD_PRIORITY_SUPPORT 0 + +// --- Compiler Configuration ------------------------------------------------ + +// The platform's annotation for forcing a C function to be inlined. +#define SB_C_FORCE_INLINE __inline__ __attribute__((always_inline)) + +// The platform's annotation for marking a C function as suggested to be +// inlined. +#define SB_C_INLINE inline + +// The platform's annotation for marking a C function as forcibly not +// inlined. +#define SB_C_NOINLINE __attribute__((noinline)) + +// The platform's annotation for marking a symbol as exported outside of the +// current shared library. +#define SB_EXPORT_PLATFORM __attribute__((visibility("default"))) + +// The platform's annotation for marking a symbol as imported from outside of +// the current linking unit. +#define SB_IMPORT_PLATFORM + +// --- Extensions Configuration ---------------------------------------------- + +// GCC/Clang doesn't define a long long hash function, except for Android and +// Game consoles. +#define SB_HAS_LONG_LONG_HASH 0 + +// GCC/Clang doesn't define a string hash function, except for Game Consoles. +#define SB_HAS_STRING_HASH 0 + +// Desktop Linux needs a using statement for the hash functions. +#define SB_HAS_HASH_USING 0 + +// Set this to 1 if hash functions for custom types can be defined as a +// hash_value() function. Otherwise, they need to be placed inside a +// partially-specified hash struct template with an operator(). +#define SB_HAS_HASH_VALUE 0 + +// Set this to 1 if use of hash_map or hash_set causes a deprecation warning +// (which then breaks the build). +#define SB_HAS_HASH_WARNING 1 + +// The location to include hash_map on this platform. +#define SB_HASH_MAP_INCLUDE <ext/hash_map> + +// C++'s hash_map and hash_set are often found in different namespaces depending +// on the compiler. +#define SB_HASH_NAMESPACE __gnu_cxx + +// The location to include hash_set on this platform. +#define SB_HASH_SET_INCLUDE <ext/hash_set> + +// Define this to how this platform copies varargs blocks. +#define SB_VA_COPY(dest, source) va_copy(dest, source) + +// --- Filesystem Configuration ---------------------------------------------- + +// The current platform's maximum length of the name of a single directory +// entry, not including the absolute path. +#define SB_FILE_MAX_NAME 64 + +// The current platform's maximum length of an absolute path. +#define SB_FILE_MAX_PATH 4096 + +// The current platform's maximum number of files that can be opened at the +// same time by one process. +#define SB_FILE_MAX_OPEN 256 + +// The current platform's file path component separator character. This is the +// character that appears after a directory in a file path. For example, the +// absolute canonical path of the file "/path/to/a/file.txt" uses '/' as a path +// component separator character. +#define SB_FILE_SEP_CHAR '/' + +// The current platform's alternate file path component separator character. +// This is like SB_FILE_SEP_CHAR, except if your platform supports an alternate +// character, then you can place that here. For example, on windows machines, +// the primary separator character is probably '\', but the alternate is '/'. +#define SB_FILE_ALT_SEP_CHAR '/' + +// The current platform's search path component separator character. When +// specifying an ordered list of absolute paths of directories to search for a +// given reason, this is the character that appears between entries. For +// example, the search path of "/etc/search/first:/etc/search/second" uses ':' +// as a search path component separator character. +#define SB_PATH_SEP_CHAR ':' + +// The string form of SB_FILE_SEP_CHAR. +#define SB_FILE_SEP_STRING "/" + +// The string form of SB_FILE_ALT_SEP_CHAR. +#define SB_FILE_ALT_SEP_STRING "/" + +// The string form of SB_PATH_SEP_CHAR. +#define SB_PATH_SEP_STRING ":" + +// --- Memory Configuration -------------------------------------------------- + +// The memory page size, which controls the size of chunks on memory that +// allocators deal with, and the alignment of those chunks. This doesn't have to +// be the hardware-defined physical page size, but it should be a multiple of +// it. +#define SB_MEMORY_PAGE_SIZE 4096 + +// Whether this platform has and should use an MMAP function to map physical +// memory to the virtual address space. +#define SB_HAS_MMAP 1 + +// Whether this platform can map executable memory. Implies SB_HAS_MMAP. This is +// required for platforms that want to JIT. +#define SB_CAN_MAP_EXECUTABLE_MEMORY 1 + +// Whether this platform has and should use an growable heap (e.g. with sbrk()) +// to map physical memory to the virtual address space. +#define SB_HAS_VIRTUAL_REGIONS 0 + +// Specifies the alignment for IO Buffers, in bytes. Some low-level network APIs +// may require buffers to have a specific alignment, and this is the place to +// 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)) + +// Defines the path where memory debugging logs should be written to. +#define SB_MEMORY_LOG_PATH "/tmp/starboard" + +// --- Thread Configuration -------------------------------------------------- + +// Defines the maximum number of simultaneous threads for this platform. Some +// platforms require sharing thread handles with other kinds of system handles, +// like mutexes, so we want to keep this managable. +#define SB_MAX_THREADS 90 + +// 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; + +// --- Graphics Configuration ------------------------------------------------ + +// Specifies whether this platform supports a performant accelerated blitter +// API. The basic requirement is a scaled, clipped, alpha-blended blit. +#define SB_HAS_BLITTER 0 + +// Specifies the preferred byte order of color channels in a pixel. Refer to +// starboard/configuration.h for the possible values. EGL/GLES platforms should +// generally prefer a byte order of RGBA, regardless of endianness. +#define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_RGBA + +// Indicates whether or not the given platform supports bilinear filtering. +// This can be checked to enable/disable renderer tests that verify that this is +// working properly. +#define SB_HAS_BILINEAR_FILTERING_SUPPORT 1 + +// Indicates whether or not the given platform supports rendering of NV12 +// textures. These textures typically originate from video decoders. +#define SB_HAS_NV12_TEXTURE_SUPPORT 1 + +// Whether the current platform should frequently flip their display buffer. +// If this is not required (e.g. SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER is set +// to 0), then optimizations where the display buffer is not flipped if the +// scene hasn't changed are enabled. +#define SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER 0 + +// --- Media Configuration --------------------------------------------------- + +// Specifies whether this platform has support for a possibly-decrypting +// elementary stream player for at least H.264/AAC (and AES-128-CTR, if +// decrypting). A player is responsible for ingesting an audio and video +// elementary stream, optionally-encrypted, and ultimately producing +// synchronized audio/video. If a player is defined, it must choose one of the +// supported composition methods below. +#define SB_HAS_PLAYER 1 + +// Specifies whether this platform's player will produce an OpenGL texture that +// the client must draw every frame with its graphics rendering. It may be that +// we get a texture handle, but cannot perform operations like GlReadPixels on +// it if it is DRM-protected. +#define SB_IS_PLAYER_PRODUCING_TEXTURE 0 + +// Specifies whether this platform's player is composited with a formal +// compositor, where the client must specify how video is to be composited into +// the graphicals scene. +#define SB_IS_PLAYER_COMPOSITED 0 + +// Specifies whether this platform's player uses a "punch-out" model, where +// video is rendered to the far background, and the graphics plane is +// automatically composited on top of the video by the platform. The client must +// punch an alpha hole out of the graphics plane for video to show through. In +// this case, changing the video bounds must be tightly synchronized between the +// player and the graphics plane. +#define SB_IS_PLAYER_PUNCHED_OUT 1 + +// Specifies the maximum amount of memory used by audio buffers of media source +// before triggering a garbage collection. A large value will cause more memory +// being used by audio buffers but will also make JavaScript app less likely to +// re-download audio data. Note that the JavaScript app may experience +// significant difficulty if this value is too low. +#define SB_MEDIA_SOURCE_BUFFER_STREAM_AUDIO_MEMORY_LIMIT (3U * 1024U * 1024U) + +// Specifies the maximum amount of memory used by video buffers of media source +// before triggering a garbage collection. A large value will cause more memory +// being used by video buffers but will also make JavaScript app less likely to +// re-download video data. Note that the JavaScript app may experience +// significant difficulty if this value is too low. +#define SB_MEDIA_SOURCE_BUFFER_STREAM_VIDEO_MEMORY_LIMIT (16U * 1024U * 1024U) + +// Specifies how much memory to reserve up-front for the main media buffer +// (usually resides inside the CPU memory) used by media source and demuxers. +// The main media buffer can work in one of the following two ways: +// 1. If GPU buffer is used (i.e. SB_MEDIA_GPU_BUFFER_BUDGET is non-zero), the +// main buffer will be used as a cache so a media buffer will be copied from +// GPU memory to main memory before sending to the decoder for further +// processing. In this case this macro should be set to a value that is +// large enough to hold all media buffers being decoded. +// 2. If GPU buffer is not used (i.e. SB_MEDIA_GPU_BUFFER_BUDGET is zero) all +// media buffers will reside in the main memory buffer. In this case the +// macro should be set to a value that is greater than the sum of the above +// source buffer stream memory limits with extra room to take account of +// fragmentations and memory used by demuxers. +#define SB_MEDIA_MAIN_BUFFER_BUDGET (32U * 1024U * 1024U) + +// Specifies how much GPU memory to reserve up-front for media source buffers. +// This should only be set to non-zero on system with limited CPU memory and +// excess GPU memory so the app can store media buffer in GPU memory. +// SB_MEDIA_MAIN_BUFFER_BUDGET has to be set to a non-zero value to avoid +// media buffers being decoded when being stored in GPU. +#define SB_MEDIA_GPU_BUFFER_BUDGET 0U + +// Specifies whether this platform has webm/vp9 support. This should be set to +// non-zero on platforms with webm/vp9 support. +#define SB_HAS_MEDIA_WEBM_VP9_SUPPORT 0 + +// Specifies the stack size for threads created inside media stack. Set to 0 to +// use the default thread stack size. Set to non-zero to explicitly set the +// stack size for media stack threads. +#define SB_MEDIA_THREAD_STACK_SIZE 0U + +// --- Decoder-only Params --- + +// Specifies how media buffers must be aligned on this platform as some +// decoders may have special requirement on the alignment of buffers being +// decoded. +#define SB_MEDIA_BUFFER_ALIGNMENT 128U + +// Specifies how video frame buffers must be aligned on this platform. +#define SB_MEDIA_VIDEO_FRAME_ALIGNMENT 256U + +// The encoded video frames are compressed in different ways, their decoding +// time can vary a lot. Occasionally a single frame can take longer time to +// decode than the average time per frame. The player has to cache some frames +// to account for such inconsistency. The number of frames being cached are +// controlled by the following two macros. +// +// Specify the number of video frames to be cached before the playback starts. +// Note that set this value too large may increase the playback start delay. +#define SB_MEDIA_MAXIMUM_VIDEO_PREROLL_FRAMES 4 + +// Specify the number of video frames to be cached during playback. A large +// value leads to more stable fps but also causes the app to use more memory. +#define SB_MEDIA_MAXIMUM_VIDEO_FRAMES 12 + +// --- Network Configuration ------------------------------------------------- + +// Specifies whether this platform supports IPV6. +#define SB_HAS_IPV6 1 + +// Specifies whether this platform supports pipe. +#define SB_HAS_PIPE 1 + +// --- Tuneable Parameters --------------------------------------------------- + +// Specifies the network receive buffer size in bytes, set via +// SbSocketSetReceiveBufferSize(). +// +// Setting this to 0 indicates that SbSocketSetReceiveBufferSize() should +// not be called. Use this for OSs (such as Linux) where receive buffer +// auto-tuning is better. +// +// On some platforms, this may affect max TCP window size which may +// dramatically affect throughput in the presence of latency. +// +// If your platform does not have a good TCP auto-tuning mechanism, +// a setting of (128 * 1024) here is recommended. +#define SB_NETWORK_RECEIVE_BUFFER_SIZE (0) + +// --- User Configuration ---------------------------------------------------- + +// The maximum number of users that can be signed in at the same time. +#define SB_USER_MAX_SIGNED_IN 1 + +// --- Timing API ------------------------------------------------------------ + +// Whether this platform has an API to retrieve how long the current thread +// has spent in the executing state. +#define SB_HAS_TIME_THREAD_NOW 1 + +// --- Platform Specific Audits ---------------------------------------------- + +#if !defined(__GNUC__) +#error "CREATOR_SHARED builds need a GCC-like compiler (for the moment)." +#endif + +#endif // STARBOARD_CREATOR_SHARED_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/creator/shared/gyp_configuration.gypi b/src/starboard/creator/shared/gyp_configuration.gypi new file mode 100644 index 0000000..c6cb2f2 --- /dev/null +++ b/src/starboard/creator/shared/gyp_configuration.gypi
@@ -0,0 +1,153 @@ +# Copyright 2016 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. + +{ + 'variables': { + 'target_arch': 'mips', + 'target_os': 'linux', + + 'enable_webdriver': 0, + 'in_app_dial%': 0, + 'gl_type%': 'system_gles3', + 'image_cache_size_in_bytes': 32 * 1024 * 1024, + + 'scratch_surface_cache_size_in_bytes' : 0, + + # This should have a default value in cobalt/base.gypi. See the comment + # there for acceptable values for this variable. + 'javascript_engine': 'mozjs', + 'cobalt_enable_jit': 0, + + # Define platform specific compiler and linker flags. + # Refer to base.gypi for a list of all available variables. + 'compiler_flags_host': [ + '-O2', + ], + 'compiler_flags': [ + # We'll pretend not to be Linux, but Starboard instead. + '-U__linux__', + '--sysroot=<(sysroot)', + '-EL', + + # Suppress some warnings that will be hard to fix. + '-Wno-unused-local-typedefs', + '-Wno-unused-result', + '-Wno-deprecated-declarations', + '-Wno-missing-field-initializers', + '-Wno-comment', + '-Wno-narrowing', + '-Wno-unknown-pragmas', + '-Wno-type-limits', # TODO: We should actually look into these. + ], + 'linker_flags': [ + '--sysroot=<(sysroot)', + '-EL', + + # We don't wrap these symbols, but this ensures that they aren't + # linked in. + '-Wl,--wrap=malloc', + '-Wl,--wrap=calloc', + '-Wl,--wrap=realloc', + '-Wl,--wrap=memalign', + '-Wl,--wrap=reallocalign', + '-Wl,--wrap=free', + '-Wl,--wrap=strdup', + '-Wl,--wrap=malloc_usable_size', + '-Wl,--wrap=malloc_stats_fast', + '-Wl,--wrap=__cxa_demangle', + ], + 'compiler_flags_debug': [ + '-O0', + ], + 'compiler_flags_cc_debug': [ + '-frtti', + ], + 'compiler_flags_devel': [ + '-O2', + ], + 'compiler_flags_cc_devel': [ + '-frtti', + ], + 'compiler_flags_qa': [ + '-O2', + ], + 'compiler_flags_cc_qa': [ + '-fno-rtti', + ], + 'compiler_flags_gold': [ + '-O2', + ], + 'compiler_flags_cc_gold': [ + '-fno-rtti', + ], + 'platform_libraries': [ + '-lasound', + '-lavcodec', + '-lavformat', + '-lavresample', + '-lavutil', + '-lm', + '-lpthread', + '-lpulse', + '-lrt', + ], + 'conditions': [ + ['cobalt_fastbuild==0', { + 'compiler_flags_debug': [ + '-g', + ], + 'compiler_flags_devel': [ + '-g', + ], + 'compiler_flags_qa': [ + ], + 'compiler_flags_gold': [ + ], + }], + ], + }, + + 'target_defaults': { + 'defines': [ + # Cobalt on Linux flag + 'COBALT_LINUX', + '__STDC_FORMAT_MACROS', # so that we get PRI* + # Enable GNU extensions to get prototypes like ffsl. + '_GNU_SOURCE=1', + ], + 'cflags_c': [ + '-std=c11', + ], + 'cflags_cc': [ + '-std=gnu++11', + '-Wno-literal-suffix', + ], + 'target_conditions': [ + ['cobalt_code==1', { + 'cflags': [ + '-Wall', + '-Wextra', + '-Wunreachable-code', + ], + },{ + 'cflags': [ + # Do not warn about unused function params. + '-Wno-unused-parameter', + # Do not warn for implicit type conversions that may change a value. + '-Wno-conversion', + ], + }], + ], + }, # end of target_defaults +}
diff --git a/src/starboard/creator/shared/gyp_configuration.py b/src/starboard/creator/shared/gyp_configuration.py new file mode 100644 index 0000000..373a70e --- /dev/null +++ b/src/starboard/creator/shared/gyp_configuration.py
@@ -0,0 +1,73 @@ +# Copyright 2016 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. +"""Starboard Creator Ci20 platform configuration for gyp_cobalt.""" + +import logging +import os +import sys + +import config.starboard + + +class PlatformConfig(config.starboard.PlatformConfigStarboard): + """Starboard ci20 platform configuration.""" + + def __init__(self, platform): + super(PlatformConfig, self).__init__(platform) + + def _GetCi20Home(self): + try: + ci20_home = os.environ['CI20_HOME'] + except KeyError: + logging.critical('ci20 builds require the `CI20_HOME\' ' + 'environment variable to be set.') + sys.exit(1) + return ci20_home + + def GetVariables(self, configuration): + ci20_home = self._GetCi20Home() + + relative_sysroot = os.path.join('mips-mti-linux-gnu', '2016.05-03', + 'sysroot') + sysroot = os.path.join(ci20_home, relative_sysroot) + + if not os.path.isdir(sysroot): + logging.critical( + 'ci20 builds require $CI20_HOME/%s to be a valid directory.', + relative_sysroot) + sys.exit(1) + variables = super(PlatformConfig, self).GetVariables(configuration) + variables.update({ + 'clang': 0, + 'sysroot': sysroot, + }) + + return variables + + def GetEnvironmentVariables(self): + ci20_home = self._GetCi20Home() + + toolchain_bin_dir = os.path.join(ci20_home, 'mips-mti-linux-gnu', + '2016.05-03', 'bin') + + env_variables = { + 'CC': os.path.join(toolchain_bin_dir, 'mips-mti-linux-gnu-gcc'), + 'CXX': os.path.join(toolchain_bin_dir, 'mips-mti-linux-gnu-g++'), + 'CC_host': 'gcc', + 'CXX_host': 'g++', + 'LD_host': 'g++', + 'ARFLAGS_host': 'rcs', + 'ARTHINFLAGS_host': 'rcsT', + } + return env_variables
diff --git a/src/starboard/decode_target.h b/src/starboard/decode_target.h new file mode 100644 index 0000000..557c511 --- /dev/null +++ b/src/starboard/decode_target.h
@@ -0,0 +1,307 @@ +// Copyright 2016 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. + +// Module Overview: Starboard Decode Target module +// +// A target for decoding image and video data into. This corresponds roughly to +// an EGLImage, but that extension may not be fully supported on all GL +// platforms. SbDecodeTarget supports multi-plane targets. We need a mechanism +// for SbBlitter as well, and this is able to more-or-less unify the two. +// +// An SbDecodeTarget can be passed into any function which decodes video or +// image data. This allows the application to allocate fast graphics memory, and +// have decoding done directly into this memory, avoiding unnecessary memory +// copies, and also avoiding pushing data between CPU and GPU memory +// unnecessarily. +// +// #### SbDecodeTargetFormat +// +// SbDecodeTargets support several different formats that can be used to decode +// into and render from. Some formats may be easier to decode into, and others +// may be easier to render. Some may take less memory. Each decoder needs to +// support the SbDecodeTargetFormat passed into it, or the decode will produce +// an error. Each decoder provides a way to check if a given +// SbDecodeTargetFormat is supported by that decoder. +// +// #### SbDecodeTargetProvider +// +// Some components may need to acquire SbDecodeTargets compatible with a certain +// rendering context, which may need to be created on a particular thread. The +// SbDecodeTargetProvider is a way for the primary rendering context to provide +// an interface that can create SbDecodeTargets on demand by other system. +// +// The primary usage is likely to be the the SbPlayer implementation on some +// platforms. +// +// #### SbDecodeTarget Example +// +// Let's say there's an image decoder for .foo files: +// +// bool SbImageDecodeFooSupportsFormat(SbDecodeTargetFormat format); +// SbDecodeTarget SbImageDecodeFoo(void* data, int data_size, +// SbDecodeTargetFormat format); +// +// First, the client should enumerate which SbDecodeTargetFormats are supported +// by that decoder. +// +// SbDecodeTargetFormat kPreferredFormats[] = { +// kSbDecodeTargetFormat3PlaneYUVI420, +// kSbDecodeTargetFormat1PlaneRGBA, +// kSbDecodeTargetFormat1PlaneBGRA, +// }; +// +// SbDecodeTargetFormat format = kSbDecodeTargetFormatInvalid; +// for (int i = 0; i < SB_ARRAY_SIZE_INT(kPreferredFormats); ++i) { +// if (SbImageDecodeFooSupportsFormat(kPreferredFormats[i])) { +// format = kPreferredFormats[i]; +// break; +// } +// } +// +// Now that the client has a format, it can create a decode target that it +// will use to decode the .foo file into. Let's assume format is +// kSbDecodeTargetFormat1PlaneRGBA, that we are on an EGL/GLES2 platform. +// Also, we won't do any error checking, to keep things even simpler. +// +// SbDecodeTarget target = SbImageDecodeFoo(encoded_foo_data, +// encoded_foo_data_size, format); +// +// // If the decode works, you can get the texture out and render it. +// GLuint texture = +// SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA); +// + +#ifndef STARBOARD_DECODE_TARGET_H_ +#define STARBOARD_DECODE_TARGET_H_ + +#include "starboard/configuration.h" +#include "starboard/export.h" +#include "starboard/types.h" + +#if SB_VERSION(3) + +#if SB_HAS(BLITTER) +#include "starboard/blitter.h" +#elif SB_HAS(GLES2) // SB_HAS(BLITTER) +#include <EGL/egl.h> +#include <GLES2/gl2.h> +#endif // SB_HAS(BLITTER) + +#ifdef __cplusplus +extern "C" { +#endif + +// --- Types ----------------------------------------------------------------- + +// Private structure representing a target for image data decoding. +typedef struct SbDecodeTargetPrivate SbDecodeTargetPrivate; + +// A handle to a target for image data decoding. +typedef SbDecodeTargetPrivate* SbDecodeTarget; + +// The list of all possible decoder target formats. An SbDecodeTarget consists +// of one or more planes of data, each plane corresponding with a surface. For +// some formats, different planes will be different sizes for the same +// dimensions. +// +// NOTE: For enumeration entries with an alpha component, the alpha will always +// be premultiplied unless otherwise explicitly specified. +typedef enum SbDecodeTargetFormat { + // A decoder target format consisting of a single RGBA plane, in that channel + // order. + kSbDecodeTargetFormat1PlaneRGBA, + + // A decoder target format consisting of a single BGRA plane, in that channel + // order. + kSbDecodeTargetFormat1PlaneBGRA, + + // A decoder target format consisting of Y and interleaved UV planes, in that + // plane and channel order. + kSbDecodeTargetFormat2PlaneYUVNV12, + + // A decoder target format consisting of Y, U, and V planes, in that order. + kSbDecodeTargetFormat3PlaneYUVI420, + + // An invalid decode target format. + kSbDecodeTargetFormatInvalid, +} SbDecodeTargetFormat; + +// All the planes supported by SbDecodeTarget. +typedef enum SbDecodeTargetPlane { + // The RGBA plane for the RGBA format. + kSbDecodeTargetPlaneRGBA = 0, + + // The BGRA plane for the BGRA format. + kSbDecodeTargetPlaneBGRA = 0, + + // The Y plane for multi-plane YUV formats. + kSbDecodeTargetPlaneY = 0, + + // The UV plane for 2-plane YUV formats. + kSbDecodeTargetPlaneUV = 1, + + // The U plane for 3-plane YUV formats. + kSbDecodeTargetPlaneU = 1, + + // The V plane for 3-plane YUV formats. + kSbDecodeTargetPlaneV = 2, +} SbDecodeTargetPlane; + +// A function that can produce an SbDecodeTarget of the given |format|, |width|, +// and |height|. +typedef SbDecodeTarget (*SbDecodeTargetAcquireFunction)( + void* context, + SbDecodeTargetFormat format, + int width, + int height); + +// A function that can reclaim an SbDecodeTarget allocated with an +// SbDecodeTargetAcquireFunction. +typedef void (*SbDecodeTargetReleaseFunction)(void* context, + SbDecodeTarget decode_target); + +// An object that can acquire and release SbDecodeTarget instances. +typedef struct SbDecodeTargetProvider { + // The function to acquire a new SbDecodeTarget from the provider. + SbDecodeTargetAcquireFunction acquire; + + // The function to release an acquired SbDecodeTarget back to the provider. + SbDecodeTargetReleaseFunction release; + + // |context| will be passed into every call to |acquire| and |release|. + void* context; +} SbDecodeTargetProvider; + +// --- Constants ------------------------------------------------------------- + +// Well-defined value for an invalid decode target handle. +#define kSbDecodeTargetInvalid ((SbDecodeTarget)NULL) + +// --- Functions ------------------------------------------------------------- + +// Returns whether the given file handle is valid. +static SB_C_INLINE bool SbDecodeTargetIsValid(SbDecodeTarget handle) { + return handle != kSbDecodeTargetInvalid; +} + +// Returns whether a given format is valid. +static SB_C_INLINE bool SbDecodeTargetIsFormatValid( + SbDecodeTargetFormat format) { + return format != kSbDecodeTargetFormatInvalid; +} + +#if SB_HAS(BLITTER) +// Creates a new SbBlitter-compatible SbDecodeTarget from one or more |planes| +// created from |display|. +// +// |format|: The format of the decode target being created. +// |planes|: An array of SbBlitterSurface handles to be bundled into an +// SbDecodeTarget. Must not be NULL. Is expected to have the same number of +// entries as the number of planes for |format|, in the order and size expected +// by that format. +SB_EXPORT SbDecodeTarget SbDecodeTargetCreate(SbDecodeTargetFormat format, + SbBlitterSurface* planes); + +// Gets the surface that represents the given plane. +SB_EXPORT SbBlitterSurface SbDecodeTargetGetPlane(SbDecodeTarget decode_target, + SbDecodeTargetPlane plane); +#elif SB_HAS(GLES2) // SB_HAS(BLITTER) +// Creates a new EGL/GLES2-compatible SbDecodeTarget from one or more |planes| +// owned by |context|, created from |display|. Must be called from a thread +// where |context| is current. +// +// |display|: The EGLDisplay being targeted. +// |context|: The EGLContext used for this operation, or EGL_NO_CONTEXT if a +// context is not required. +// |format|: The format of the decode target being created. +// |planes|: An array of GLES Texture handles to be bundled into an +// SbDecodeTarget. Must not be NULL. Is expected to have the same number of +// entries as the number of planes for |format|, in the order and size expected +// by that format. +SB_EXPORT SbDecodeTarget SbDecodeTargetCreate(EGLDisplay display, + EGLContext context, + SbDecodeTargetFormat format, + GLuint* planes); + +// Gets the texture that represents the given plane. +SB_EXPORT GLuint SbDecodeTargetGetPlane(SbDecodeTarget decode_target, + SbDecodeTargetPlane plane); + +#else // SB_HAS(BLITTER) + +// Stub function for when graphics aren't enabled. Always creates +// kSbDecodeTargetInvalid. +static SB_C_INLINE SbDecodeTarget +SbDecodeTargetCreate(SbDecodeTargetFormat format) { + SB_UNREFERENCED_PARAMETER(format); + return kSbDecodeTargetInvalid; +} + +// Stub function for when graphics aren't enabled. There is no concept of a +// plane, and |NULL| is always returned. +static SB_C_INLINE void* SbDecodeTargetGetPlane(SbDecodeTarget decode_target, + SbDecodeTargetPlane plane) { + SB_UNREFERENCED_PARAMETER(decode_target); + SB_UNREFERENCED_PARAMETER(plane); + return NULL; +} + +#endif // SB_HAS(BLITTER) + +// Destroys the given SbDecodeTarget. Note that calling this function does NOT +// destroy the associated surfaces with it, in order to accommodate the case +// in which it is desirable for the lifetime of the surface to outlive the +// SbDecodeTarget that contains it. If the surfaces should be destroyed along +// with the SbDecodeTarget, then they should be extracted with +// |SbDecodeTargetGetPlane| and destroyed manually by the client. +SB_EXPORT void SbDecodeTargetDestroy(SbDecodeTarget decode_target); + +// Gets the format that |decode_target| was created with. +SB_EXPORT SbDecodeTargetFormat +SbDecodeTargetGetFormat(SbDecodeTarget decode_target); + +// Gets whether |decode_target| is opaque or not. The underlying source of +// this value is expected to be properly maintained by the Starboard +// implementation. So, for example, if an opaque only image type were decoded +// into an SbDecodeTarget, then the implementation would configure things in +// such a way that this function would return true. By opaque, it is meant +// that all alpha values are guaranteed to be 255, if |decode_target| is of a +// format that has alpha values. If |decode_target| is of a format that does +// not have alpha values, then this function should return |true|. +SB_EXPORT bool SbDecodeTargetIsOpaque(SbDecodeTarget decode_target); + +// Inline convenience function to acquire an SbDecodeTarget of type |format|, +// |width|, and |height| from |provider|. +static SB_C_INLINE SbDecodeTarget +SbDecodeTargetAcquireFromProvider(SbDecodeTargetProvider* provider, + SbDecodeTargetFormat format, + int width, + int height) { + return provider->acquire(provider->context, format, width, height); +} + +// Inline convenience function to release |decode_target| back to |provider|. +static SB_C_INLINE void SbDecodeTargetReleaseToProvider( + SbDecodeTargetProvider* provider, + SbDecodeTarget decode_target) { + provider->release(provider->context, decode_target); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SB_VERSION(3) + +#endif // STARBOARD_DECODE_TARGET_H_
diff --git a/src/starboard/directory.h b/src/starboard/directory.h index 728b875..0147306 100644 --- a/src/starboard/directory.h +++ b/src/starboard/directory.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Directory listing API. +// Module Overview: Starboard Directory module +// +// Provides directory listing functions. #ifndef STARBOARD_DIRECTORY_H_ #define STARBOARD_DIRECTORY_H_ @@ -46,26 +48,43 @@ return directory != kSbDirectoryInvalid; } -// Opens the given existing directory for listing. Will return -// kSbDirectoryInvalidHandle if not successful. If |out_error| is provided by -// the caller, it will be set to the appropriate SbFileError code on failure. +// Opens the given existing directory for listing. This function returns +// kSbDirectoryInvalidHandle if it is not successful. +// +// If |out_error| is provided by the caller, it will be set to the appropriate +// SbFileError code on failure. +// +// |out_error|: An output parameter that, in case of an error, is set to the +// reason that the directory could not be opened. SB_EXPORT SbDirectory SbDirectoryOpen(const char* path, SbFileError* out_error); -// Closes an open directory stream handle. Returns whether the close was -// successful. +// Closes an open directory stream handle. The return value indicates whether +// the directory was closed successfully. +// +// |directory|: The directory stream handle to close. SB_EXPORT bool SbDirectoryClose(SbDirectory directory); -// Populates |out_entry| with the next entry in that directory stream, and moves -// the stream forward by one entry. Returns |true| if there was a next -// directory, and |false| at the end of the directory stream. +// 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. +// +// |directory|: The directory stream from which to retrieve the next directory. +// |out_entry|: The variable to be populated with the next directory entry. SB_EXPORT bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry* out_entry); -// Returns whether SbDirectoryOpen is allowed for the given |path|. +// Indicates whether SbDirectoryOpen is allowed for the given |path|. +// +// |path|: The path to be checked. SB_EXPORT bool SbDirectoryCanOpen(const char* path); // Creates the directory |path|, assuming the parent directory already exists. -// Returns whether the directory now exists (even if it existed before). +// This function returns |true| if the directory now exists (even if it existed +// before) and returns |false| if the directory does not exist. +// +// |path|: The path to be created. SB_EXPORT bool SbDirectoryCreate(const char* path); #ifdef __cplusplus
diff --git a/src/starboard/double.h b/src/starboard/double.h index bde6acc..8a28bbc 100644 --- a/src/starboard/double.h +++ b/src/starboard/double.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Double-precision floating point helper functions. +// Module Overview: Starboard Double module +// +// Provides double-precision floating point helper functions. #ifndef STARBOARD_DOUBLE_H_ #define STARBOARD_DOUBLE_H_ @@ -25,21 +27,32 @@ #endif // Floors double-precision floating-point number |d| to the nearest integer. +// +// |d|: The number to be floored. SB_EXPORT double SbDoubleFloor(const double d); // Returns the absolute value of the given double-precision floating-point -// number |d|, preserving NaN and Infinity. +// number |d|, preserving |NaN| and infinity. +// +// |d|: The number to be adjusted. SB_EXPORT double SbDoubleAbsolute(const double d); // Returns |base| taken to the power of |exponent|. +// +// |base|: The number to be adjusted. +// |exponent|: The power to which the |base| number should be raised. SB_EXPORT double SbDoubleExponent(const double base, const double exponent); // Determines whether double-precision floating-point number |d| represents a -// fininte number. +// finite number. +// +// |d|: The number to be evaluated. SB_EXPORT bool SbDoubleIsFinite(const double d); -// Determines whether double-precision floating-point number |d| represents "Not -// a Number." +// Determines whether double-precision floating-point number |d| represents +// "Not a Number." +// +// |d|: The number to be evaluated. SB_EXPORT bool SbDoubleIsNan(const double d); #ifdef __cplusplus
diff --git a/src/starboard/drm.h b/src/starboard/drm.h index 26d99a9..f7f4544 100644 --- a/src/starboard/drm.h +++ b/src/starboard/drm.h
@@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Definitions that allow for DRM support, common between Player and Decoder -// interfaces. +// Module Overview: Starboard DRM module +// +// Provides definitions that allow for DRM support, which are common +// between Player and Decoder interfaces. #ifndef STARBOARD_DRM_H_ #define STARBOARD_DRM_H_ @@ -104,26 +106,30 @@ // --- Functions ------------------------------------------------------------- -// Returns whether the |drm_system| is a valid SbDrmSystem. +// Indicates whether |drm_system| is a valid SbDrmSystem. static SB_C_FORCE_INLINE bool SbDrmSystemIsValid(SbDrmSystem drm) { return drm != kSbDrmSystemInvalid; } -// Creates a new |key_system| DRM system that can be used when constructing an -// SbPlayer or an SbDecoder. |key_system| should be in fhe form of -// "com.example.somesystem" as suggested by -// https://w3c.github.io/encrypted-media/#key-system. All letters in -// |key_system| should be in lower case and will be matched exactly with known -// DRM key systems of the platform. -// |context| will be passed when any callback parameters of this function are -// called. -// |update_request_callback| is a callback that will be called every time after -// SbDrmGenerateSessionUpdateRequest() is called. -// |session_updated_callback| is a callback that will be called every time -// after SbDrmUpdateSession() is called. -// Please refer to the document of SbDrmGenerateSessionUpdateRequest() and +// Creates a new DRM system that can be used when constructing an SbPlayer +// or an SbDecoder. +// +// This function returns kSbDrmSystemInvalid if |key_system| is unsupported. +// +// Also see the documentation of SbDrmGenerateSessionUpdateRequest() and // SbDrmUpdateSession() for more details. -// Returns kSbDrmSystemInvalid if the |key_system| is unsupported. +// +// |key_system|: The DRM key system to be created. The value should be in the +// form of "com.example.somesystem" as suggested by +// https://w3c.github.io/encrypted-media/#key-system. All letters in the value +// should be lowercase and will be matched exactly with known DRM key systems +// of the platform. +// |context|: A value passed when any of this function's callback parameters +// are called. +// |update_request_callback|: A function that is called every time after +// SbDrmGenerateSessionUpdateRequest() is called. +// |session_updated_callback|: A function that is called every time after +// SbDrmUpdateSession() is called. SB_EXPORT SbDrmSystem SbDrmCreateSystem(const char* key_system, void* context, @@ -132,15 +138,28 @@ // Asynchronously generates a session update request payload for // |initialization_data|, of |initialization_data_size|, in case sensitive -// |type|, extracted from the media stream, in |drm_system|'s key system. Calls -// |update_request_callback| with |context| and either a populated request, or -// NULL |session_id| if an error occured. |context| may be used to distinguish -// callbacks from multiple concurrent calls to -// SbDrmGenerateSessionUpdateRequest(), and/or to route callbacks back to an -// object instance. +// |type|, extracted from the media stream, in |drm_system|'s key system. // -// Callbacks may called from another thread or from the current thread before -// this function returns. +// This function calls |drm_system|'s |update_request_callback| function, +// which is defined when the DRM system is created by SbDrmCreateSystem. When +// calling that function, this function either sends |context| (also from +// |SbDrmCreateSystem|) and a populated request, or it sends NULL |session_id| +// if an error occurred. +// +// |drm_system|'s |context| may be used to distinguish callbacks from +// multiple concurrent calls to SbDrmGenerateSessionUpdateRequest(), and/or +// to route callbacks back to an object instance. +// +// Callbacks may be called either from the current thread before this function +// returns or from another thread. +// +// |drm_system|: The DRM system that defines the key system used for the +// session update request payload as well as the callback function that is +// called as a result of the function being called. +// |type|: The case-sensitive type of the session update request payload. +// |initialization_data|: The data for which the session update request payload +// is created. +// |initialization_data_size|: The size of the session update request payload. SB_EXPORT void SbDrmGenerateSessionUpdateRequest( SbDrmSystem drm_system, const char* type, @@ -155,26 +174,28 @@ // and/or to route callbacks back to an object instance. // // Once the session is successfully updated, an SbPlayer or SbDecoder associated -// with that system will be able to decrypt samples encrypted. +// with that DRM key system will be able to decrypt encrypted samples. // -// |session_updated_callback| may called from another thread or from the current -// thread before this function returns. +// |drm_system|'s |session_updated_callback| may called either from the +// current thread before this function returns or from another thread. SB_EXPORT void SbDrmUpdateSession(SbDrmSystem drm_system, const void* key, int key_size, const void* session_id, int session_id_size); -// Clear any internal states/resources related to the particular |session_id|. +// Clear any internal states/resources related to the specified |session_id|. SB_EXPORT void SbDrmCloseSession(SbDrmSystem drm_system, const void* session_id, int session_id_size); -// Gets the number of keys installed in the given |drm_system| system. +// Returns the number of keys installed in |drm_system|. +// +// |drm_system|: The system for which the number of installed keys is retrieved. SB_EXPORT int SbDrmGetKeyCount(SbDrmSystem drm_system); -// Gets the |out_key|, |out_key_size|, and |out_status| for key with |index| in -// the given |drm_system| system. Returns whether a key is installed at |index|. +// Gets |out_key|, |out_key_size|, and |out_status| for the key with |index| +// in |drm_system|. Returns whether a key is installed at |index|. // If not, the output parameters, which all must not be NULL, will not be // modified. SB_EXPORT bool SbDrmGetKeyStatus(SbDrmSystem drm_system, @@ -186,17 +207,21 @@ SbDrmKeyStatus* out_status); // Removes all installed keys for |drm_system|. Any outstanding session update -// requests will also be invalidated. +// requests are also invalidated. +// +// |drm_system|: The DRM system for which keys should be removed. SB_EXPORT void SbDrmRemoveAllKeys(SbDrmSystem drm_system); -// Destroys |drm_system|, which implicitly removes all keys installed in it, and +// Destroys |drm_system|, which implicitly removes all keys installed in it and // invalidates all outstanding session update requests. A DRM system cannot be // destroyed unless any associated SbPlayer or SbDecoder has first been // destroyed. // -// All callbacks are guaranteed to be finished when this function returns. So -// calling this function from a callback passed to SbDrmCreateSystem() will -// result in deadlock. +// All callbacks are guaranteed to be finished when this function returns. +// As a result, if this function is called from a callback that is passed +// to SbDrmCreateSystem(), a deadlock will occur. +// +// |drm_system|: The DRM system to be destroyed. SB_EXPORT void SbDrmDestroySystem(SbDrmSystem drm_system); #ifdef __cplusplus
diff --git a/src/starboard/event.h b/src/starboard/event.h index cec1884..44cdd79 100644 --- a/src/starboard/event.h +++ b/src/starboard/event.h
@@ -12,7 +12,47 @@ // See the License for the specific language governing permissions and // limitations under the License. -// The event system that wraps the Starboard main loop and entry point. +// Module Overview: Starboard Event module +// +// Defines the event system that wraps the Starboard main loop and entry point. +// +// ## The Starboard Application life cycle +// +// | * +// | | _________________________ +// | Start | | +// | | | Resume +// | V V | +// | [ STARTED ] --Pause--> [ PAUSED ] --Suspend--> [ SUSPENDED ] +// | ^ | | +// | | Unpause Stop +// | |_____________________| | +// | V +// | [ STOPPED ] +// +// The first event that a Starboard application receives is Start +// (kSbEventTypeStart). This puts the application in the |STARTED| state. +// The application is in the foreground and can expect to do all of the normal +// things it might want to do. Once in the |STARTED| state, it may receive a +// |Pause| event, putting the application into the |PAUSED| state. +// +// In the |PAUSED| state, the application is still visible, but has lost +// focus, or it is partially obscured by a modal dialog, or it is on its way +// to being shut down. The application should pause activity in this state. +// In this state, it can receive |Unpause| to be brought back to the foreground +// state (|STARTED|), or |Suspend| to be pushed further in the background +// to the |SUSPENDED| state. +// +// In the |SUSPENDED| state, the application is generally not visible. It +// should immediately release all graphics and video resources, and shut down +// all background activity (timers, rendering, etc). Additionally, the +// application should flush storage to ensure that if the application is +// killed, the storage will be up-to-date. The application may be killed at +// this point, but will ideally receive a |Stop| event for a more graceful +// shutdown. +// +// Note that the application is always expected to transition through |PAUSED| +// to |SUSPENDED| before receiving |Stop| or being killed. #ifndef STARBOARD_EVENT_H_ #define STARBOARD_EVENT_H_ @@ -26,43 +66,6 @@ extern "C" { #endif -// The Starboard Application life cycle -// ------------------------------------ -// -// * -// | _________________________ -// Start | | -// | | Resume -// V V | -// [ STARTED ] --Pause--> [ PAUSED ] --Suspend--> [ SUSPENDED ] -// ^ | | -// | Unpause Stop -// |_____________________| | -// V -// [ STOPPED ] -// -// The first event that a Starboard application will receive is Start -// (kSbEventTypeStart). This puts the application in the STARTED state; it is in -// the foreground and can expect to do all the normal things it might want to -// do. Once in the STARTED state, it may receive a Pause event, putting the -// application into the PAUSED state. -// -// In the PAUSED state, the application is still visible, but has lost focus, or -// it is partially obscured by a modal dialog, or it is on its way to being shut -// down. The application should pause activity in this state. In this state, it -// can receive Unpause to be brought back to the foreground state, STARTED, or -// Suspend to be pushed further in the background to the SUSPENDED state. -// -// In the SUSPENDED state, the application is generally not visible. It should -// immediately release all graphics and video resources, and shut down all -// background activity (timers, rendering, etc). Additionally, the application -// should flush storage to ensure that if the application is killed, the storage -// will be up-to-date. The application may be killed at this point, but will -// ideally receive a Stop event for a more graceful shutdown. -// -// Note that the application is always expected to transition through PAUSED to -// SUSPENDED before receiving Stop or being killed. - // An enumeration of all possible event types dispatched directly by the // system. Each event is accompanied by a void* data argument, and each event // must define the type of the value pointed to by that data argument, if any. @@ -185,31 +188,36 @@ return handle != kSbEventIdInvalid; } -// Declaration of the entry point that Starboard applications MUST implement. -// Any memory pointed at by |event| or the |data| field inside |event| is owned -// by the system, and will be reclaimed after this function returns, so the -// implementation must copy this data to extend its life. This should also be -// assumed of all fields within the data object, unless otherwise explicitly -// specified. This function will only be called from the main Starboard thread. -// There is no specification about what other work might happen on this thread, -// so the application should generally do as little work as possible on this -// thread, and just dispatch it over to another thread. +// The entry point that Starboard applications MUST implement. Any memory +// pointed at by |event| or the |data| field inside |event| is owned by the +// system, and that memory is reclaimed after this function returns, so the +// implementation must copy this data to extend its life. This behavior should +// also be assumed of all fields within the |data| object, unless otherwise +// explicitly specified. +// +// This function is only called from the main Starboard thread. There is no +// specification about what other work might happen on this thread, so the +// application should generally do as little work as possible on this thread, +// and just dispatch it over to another thread. SB_IMPORT void SbEventHandle(const SbEvent* event); -// Schedules an event |callback| into the main Starboard event loop, with -// accompanying |context|. This function may be called from any thread, but -// |callback| will always be called from the main Starboard thread, queued with -// other pending events. |callback| will not be called any earlier than |delay| -// microseconds from the time SbEventInject is called. Set |delay| to 0 to call -// the event as soon as possible. +// Schedules an event |callback| into the main Starboard event loop. +// This function may be called from any thread, but |callback| is always +// called from the main Starboard thread, queued with other pending events. +// +// |callback|: The callback function to be called. +// |context|: The context that is passed to the |callback| function. +// |delay|: The minimum number of microseconds to wait before calling the +// |callback| function. Set |delay| to |0| to call the callback as soon as +// possible. SB_EXPORT SbEventId SbEventSchedule(SbEventCallback callback, void* context, SbTime delay); -// Cancels the injected |event_id|. Does nothing if the event already fired. Can -// be safely called from any thread, but there is no guarantee that the event -// will not run anyway unless it is called from the main Starboard event loop -// thread. +// Cancels the specified |event_id|. Note that this function is a no-op +// if the event already fired. This function can be safely called from any +// thread, but the only way to guarantee that the event does not run anyway +// is to call it from the main Starboard event loop thread. SB_EXPORT void SbEventCancel(SbEventId event_id); #ifdef __cplusplus
diff --git a/src/starboard/examples/glclear/main.cc b/src/starboard/examples/glclear/main.cc index 7d520ec..129c06c 100644 --- a/src/starboard/examples/glclear/main.cc +++ b/src/starboard/examples/glclear/main.cc
@@ -125,7 +125,7 @@ } SB_DCHECK(surface_ != EGL_NO_SURFACE); - SbMemoryFree(configs); + SbMemoryDeallocate(configs); eglQuerySurface(display_, surface_, EGL_WIDTH, &egl_surface_width_); eglQuerySurface(display_, surface_, EGL_HEIGHT, &egl_surface_height_);
diff --git a/src/starboard/examples/window/main.cc b/src/starboard/examples/window/main.cc index 381d428..bb5bc83 100644 --- a/src/starboard/examples/window/main.cc +++ b/src/starboard/examples/window/main.cc
@@ -35,7 +35,7 @@ << ", window=" << data->window << ", device_type=" << data->device_type << ", device_id=" << data->device_id - << ", key=" << data->key + << ", key=0x" << std::hex << data->key << ", character=" << data->character << ", modifiers=0x" << std::hex << data->key_modifiers << ", location=" << std::dec << data->key_location;
diff --git a/src/starboard/export.h b/src/starboard/export.h index af2303f..b19c1c7 100644 --- a/src/starboard/export.h +++ b/src/starboard/export.h
@@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Macros to properly export or import symbols from shared libraries. +// Module Overview: Starboard Export module +// +// Provides macros for properly exporting or importing symbols from shared +// libraries. #ifndef STARBOARD_EXPORT_H_ #define STARBOARD_EXPORT_H_
diff --git a/src/starboard/file.h b/src/starboard/file.h index d1ecc11..0e92907 100644 --- a/src/starboard/file.h +++ b/src/starboard/file.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// File system Input/Output +// Module Overview: Starboard File module +// +// Defines file system input/output functions. #ifndef STARBOARD_FILE_H_ #define STARBOARD_FILE_H_ @@ -32,17 +34,33 @@ // A handle to an open file. typedef SbFilePrivate* SbFile; -// kSbFile(Open|Create)(Only|Always|Truncated) are mutually exclusive. You -// should specify exactly one of the five (possibly combining with other flags) -// when opening or creating a file. +// Flags that define how a file is used in the application. These flags should +// be or'd together when passed to SbFileOpen to open or create a file. +// +// The following five flags are mutually exclusive. You must specify exactly one +// of them: +// - |kSbFileOpenAlways| +// - |kSbFileOpenOnly| +// - |kSbFileOpenTruncated| +// - |kSbFileCreateAlways| +// - |kSbFileCreateOnly| +// +// In addition, one or more of the following flags must be specified: +// - |kSbFileRead| +// - |kSbFileWrite| +// +// The |kSbFileAsync| flag is optional. typedef enum SbFileFlags { kSbFileOpenOnly = 1 << 0, // Opens a file, only if it exists. kSbFileCreateOnly = 1 << 1, // Creates a new file, only if it // does not already exist. - kSbFileOpenAlways = 1 << 2, // May create a new file. - kSbFileCreateAlways = 1 << 3, // May overwrite an old file. - kSbFileOpenTruncated = 1 << 4, // Opens a file and truncates it - // to zero, only if it exists. + kSbFileOpenAlways = 1 << 2, // Opens an existing file at the specified + // path or creates a new file at that path. + kSbFileCreateAlways = 1 << 3, // Creates a new file at the specified path + // or overwrites an existing file at that + // path. + kSbFileOpenTruncated = 1 << 4, // Opens a file and truncates it to zero, + // only if it exists. kSbFileRead = 1 << 5, kSbFileWrite = 1 << 6, kSbFileAsync = 1 << 7, // May allow asynchronous I/O on some @@ -112,72 +130,183 @@ } // Opens the file at |path|, which must be absolute, creating it if specified by -// |flags|. |out_created|, if provided, will be set to true if a new file was -// created (or an old one truncated to zero length to simulate a new file, which -// can happen with kSbFileCreateAlways), and false otherwise. |out_error| can -// be NULL. If creation failed, it will return kSbFileInvalid. The read/write -// position is at the beginning of the file. +// |flags|. The read/write position is at the beginning of the file. +// +// Note that this function only guarantees the correct behavior when |path| +// points to a file. The behavior is undefined if |path| points to a directory. +// +// |path|: The absolute path of the file to be opened. +// |flags|: |SbFileFlags| that determine how the file is used in the +// application. Among other things, |flags| can indicate whether the +// application should create |path| if |path| does not already exist. +// |out_created|: Starboard sets this value to |true| if a new file is created +// or if an old file is truncated to zero length to simulate a new file, +// which can happen if the |kSbFileCreateAlways| flag is set. Otherwise, +// Starboard sets this value to |false|. +// |out_error|: If |path| cannot be created, this is set to |kSbFileInvalid|. +// Otherwise, it is |NULL|. SB_EXPORT SbFile SbFileOpen(const char* path, int flags, bool* out_created, SbFileError* out_error); -// Closes |file|. Returns whether the close was successful. +// Closes |file|. The return value indicates whether the file was closed +// successfully. +// +// |file|: The absolute path of the file to be closed. SB_EXPORT bool SbFileClose(SbFile file); -// Changes current read/write position in |file| to |offset| relative to the -// origin defined by |whence|. Returns the resultant current read/write position -// in the file (relative to the start) or -1 in case of error. May not support +// Changes the current read/write position in |file|. The return value +// identifies the resultant current read/write position in the file (relative +// to the start) or |-1| in case of an error. This function might not support // seeking past the end of the file on some platforms. +// +// |file|: The SbFile in which the read/write position will be changed. +// |whence|: The starting read/write position. The position is modified relative +// to this value. +// |offset|: The amount that the read/write position is changed, relative to +// |whence|. SB_EXPORT int64_t SbFileSeek(SbFile file, SbFileWhence whence, int64_t offset); // Reads |size| bytes (or until EOF is reached) from |file| into |data|, -// starting at the file's current position. Returns the number of bytes read, or -// -1 on error. Note that this function does NOT make a best effort to read all -// data on all platforms, it just reads however many bytes are quickly -// available. Can be run in a loop to make it a best-effort read. +// starting at the file's current position. +// +// The return value specifies the number of bytes read or |-1| if there was +// an error. Note that this function does NOT make a best effort to read all +// data on all platforms; rather, it just reads however many bytes are quickly +// available. However, this function can be run in a loop to make it a +// best-effort read. +// +// |file|: The SbFile from which to read data. +// |data|: The variable to which data is read. +// |size|: The amount of data (in bytes) to read. SB_EXPORT int SbFileRead(SbFile file, char* data, int size); // Writes the given buffer into |file| at the file's current position, -// overwritting any data that was previously there. Returns the number of bytes -// written, or -1 on error. Note that this function does NOT make a best effort -// to write all data, it writes however many bytes can be written quickly. It -// should be run in a loop to ensure all data is written. +// overwriting any data that was previously there. +// +// The return value identifies the number of bytes written, or |-1| on error. +// Note that this function does NOT make a best effort to write all data; +// rather, it writes however many bytes can be written quickly. Generally, this +// means that it writes however many bytes as possible without blocking on IO. +// Run this function in a loop to ensure that all data is written. +// +// |file|: The SbFile to which data will be written. +// |data|: The data to be written. +// |size|: The amount of data (in bytes) to write. SB_EXPORT int SbFileWrite(SbFile file, const char* data, int size); -// Truncates the given |file| to the given |length|. If length is greater than -// the current size of the file, the file is extended with zeros. Negative -// |length| will do nothing and return false. +// Truncates the given |file| to the given |length|. The return value indicates +// whether the file was truncated successfully. +// +// |file|: The file to be truncated. +// |length|: The expected length of the file after it is truncated. If |length| +// is greater than the current size of the file, then the file is extended +// with zeros. If |length| is negative, then the function is a no-op and +// returns |false|. SB_EXPORT bool SbFileTruncate(SbFile file, int64_t length); -// Flushes the write buffer to |file|. Data written via SbFileWrite isn't -// necessarily comitted right away until the file is flushed or closed. +// Flushes the write buffer to |file|. Data written via SbFileWrite is not +// necessarily committed until the SbFile is flushed or closed. +// +// |file|: The SbFile to which the write buffer is flushed. SB_EXPORT bool SbFileFlush(SbFile file); -// Places some information about |file|, an open SbFile, in |out_info|. Returns -// |true| if successful. If unsuccessful, |out_info| is untouched. +// Retrieves information about |file|. The return value indicates whether the +// file information was retrieved successfully. +// +// |file|: The SbFile for which information is retrieved. +// |out_info|: The variable into which the retrieved data is placed. This +// variable is not touched if the operation is not successful. SB_EXPORT bool SbFileGetInfo(SbFile file, SbFileInfo* out_info); -// Places information about the file or directory at |path|, which must be -// absolute, into |out_info|. Returns |true| if successful. If unsuccessful, -// |out_info| is untouched. +// Retrieves information about the file at |path|. The return value indicates +// whether the file information was retrieved successfully. +// +// |file|: The absolute path of the file for which information is retrieved. +// |out_info|: The variable into which the retrieved data is placed. This +// variable is not touched if the operation is not successful. SB_EXPORT bool SbFileGetPathInfo(const char* path, SbFileInfo* out_info); -// Deletes the regular file, symlink, or empty directory at |path|, which must -// be absolute. Used mainly to clean up after unit tests. May fail on some -// platforms if the file in question is being held open. +// Deletes the regular file, symlink, or empty directory at |path|. This +// function is used primarily to clean up after unit tests. On some platforms, +// this function fails if the file in question is being held open. +// +// |path|: The absolute path fo the file, symlink, or directory to be deleted. SB_EXPORT bool SbFileDelete(const char* path); -// Returns whether a file or directory exists at |path|, which must be absolute. +// Indicates whether a file or directory exists at |path|. +// +// |path|: The absolute path of the file or directory being checked. SB_EXPORT bool SbFileExists(const char* path); -// Returns whether SbFileOpen() with the given |flags| is allowed for |path|, -// which must be absolute. +// Indicates whether SbFileOpen() with the given |flags| is allowed for |path|. +// +// |path|: The absolute path to be checked. +// |flags|: The flags that are being evaluated for the given |path|. SB_EXPORT bool SbFileCanOpen(const char* path, int flags); -// Converts an ISO fopen() mode string into flags that can be equivalently +// Converts an ISO |fopen()| mode string into flags that can be equivalently // passed into SbFileOpen(). +// +// |mode|: The mode string to be converted into flags. SB_EXPORT int SbFileModeStringToFlags(const char* mode); + +// Reads |size| bytes (or until EOF is reached) from |file| into |data|, +// starting from the beginning of the file. +// +// The return value specifies the number of bytes read or |-1| if there was +// an error. Note that, unlike |SbFileRead|, this function does make a best +// effort to read all data on all platforms. As such, it is not intended for +// stream-oriented files but instead for cases when the normal expectation is +// that |size| bytes can be read unless there is an error. +// +// |file|: The SbFile from which to read data. +// |data|: The variable to which data is read. +// |size|: The amount of data (in bytes) to read. +static inline int SbFileReadAll(SbFile file, char* data, int size) { + if (!SbFileIsValid(file) || size < 0) { + return -1; + } + int bytes_read = 0; + int rv; + do { + rv = SbFileRead(file, data + bytes_read, size - bytes_read); + if (bytes_read <= 0) { + break; + } + bytes_read += rv; + } while (bytes_read < size); + + return bytes_read ? bytes_read : rv; +} + +// Writes the given buffer into |file|, starting at the beginning of the file, +// and overwriting any data that was previously there. Unlike SbFileWrite, this +// function does make a best effort to write all data on all platforms. +// +// The return value identifies the number of bytes written, or |-1| on error. +// +// |file|: The file to which data will be written. +// |data|: The data to be written. +// |size|: The amount of data (in bytes) to write. +static inline int SbFileWriteAll(SbFile file, const char* data, int size) { + if (!SbFileIsValid(file) || size < 0) { + return -1; + } + int bytes_written = 0; + int rv; + do { + rv = SbFileWrite(file, data + bytes_written, size - bytes_written); + if (bytes_written <= 0) { + break; + } + bytes_written += rv; + } while (bytes_written < size); + + return bytes_written ? bytes_written : rv; +} + #ifdef __cplusplus } // extern "C" #endif @@ -219,10 +348,18 @@ int Read(char* data, int size) const { return SbFileRead(file_, data, size); } + int ReadAll(char* data, int size) const { + return SbFileReadAll(file_, data, size); + } + int Write(const char* data, int size) const { return SbFileWrite(file_, data, size); } + int WriteAll(const char* data, int size) const { + return SbFileWriteAll(file_, data, size); + } + bool Truncate(int64_t length) const { return SbFileTruncate(file_, length); } bool Flush() const { return SbFileFlush(file_); } @@ -231,6 +368,12 @@ return SbFileGetInfo(file_, out_info); } + int64_t GetSize() const { + SbFileInfo file_info; + bool success = GetInfo(&file_info); + return (success ? file_info.size : -1); + } + private: SbFile file_; };
diff --git a/src/starboard/image.h b/src/starboard/image.h new file mode 100644 index 0000000..5ff6e3f --- /dev/null +++ b/src/starboard/image.h
@@ -0,0 +1,102 @@ +// Copyright 2016 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. + +// Module Overview: Starboard Image Decoding Module +// +// API for hardware accelerated image decoding. This module allows for the +// client to feed in raw, encoded data to be decoded directly into an +// SbDecodeTarget. It also provides an interface for the client to query what +// combinations of encoded image formats and SbDecodeTargetFormats are +// supported or not. +// +// All functions in this module are safe to call from any thread at any point +// in time. +// +// #### SbImageIsDecodeSupported and SbImageDecode Example +// +// Let's assume that we're on a Blitter platform. +// +// SbDecodeTargetProvider* provider = GetProviderFromSomewhere(); +// void* data = GetCompressedJPEGFromSomewhere(); +// int data_size = GetCompressedJPEGSizeFromSomewhere(); +// const char* mime_type = "image/jpeg"; +// SbDecodeTargetFormat format = kSbDecodeTargetFormat1PlaneRGBA; +// +// if (!SbImageIsDecodeSupported(mime_type, format)) { +// return; +// } +// +// SbDecodeTarget result_target = SbDecodeImage(provider, data, data_size, +// mime_type, format); +// SbBlitterSurface surface = +// SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA); +// // Do stuff with surface... +// + +#ifndef STARBOARD_IMAGE_H_ +#define STARBOARD_IMAGE_H_ + +#include "starboard/configuration.h" +#include "starboard/decode_target.h" +#include "starboard/export.h" +#include "starboard/types.h" + +#if SB_VERSION(3) + +#ifdef __cplusplus +extern "C" { +#endif + +// Whether the current platform supports hardware accelerated decoding an +// image of mime type |mime_type| into SbDecodeTargetFormat |format|. The +// result of this function must not change over the course of the program, +// which means that the results of this function may be cached indefinitely. +SB_EXPORT bool SbImageIsDecodeSupported(const char* mime_type, + SbDecodeTargetFormat format); + +// Attempt to decode encoded |mime_type| image data |data| of size |data_size| +// into an SbDecodeTarget of SbDecodeFormatType |format|, possibly using +// SbDecodeTargetProvider |provider|, if it is non-null. Thus, four following +// scenarios regarding the provider may happen: +// +// 1. The provider is required by the |SbImageDecode| implementation and no +// provider is given. The implementation should gracefully fail by +// immediately returning kSbDecodeTargetInvalid. +// 2. The provider is required and is passed in. The implementation will +// proceed forward, using the SbDecodeTarget from the provider. +// 3. The provider is not required and is passed in. The provider will NOT be +// called, and the implementation will proceed to decoding however it +// desires. +// 4. The provider is not required and is not passed in. The implementation +// will proceed forward. +// +// Thus, it is NOT safe for clients of this API to assume that the |provider| +// it passes in will be called. Finally, if the decode succeeds, a new +// SbDecodeTarget will be allocated. If |mime_type| image decoding for the +// requested format is not supported or the decode fails, +// kSbDecodeTargetInvalid will be returned, with any intermediate allocations +// being cleaned up in the implementation. +SB_EXPORT SbDecodeTarget SbImageDecode(SbDecodeTargetProvider* provider, + void* data, + int data_size, + const char* mime_type, + SbDecodeTargetFormat format); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SB_VERSION(3) + +#endif // STARBOARD_IMAGE_H_
diff --git a/src/starboard/input.h b/src/starboard/input.h index dec4b29..e8f98b2 100644 --- a/src/starboard/input.h +++ b/src/starboard/input.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Definition of input events and associated data types. +// Module Overview: Starboard Input module +// +// Defines input events and associated data types. #ifndef STARBOARD_INPUT_H_ #define STARBOARD_INPUT_H_ @@ -27,59 +29,60 @@ extern "C" { #endif -// All possible input subsystem types. +// Identifies possible input subsystem types. The types of events that each +// device type produces correspond to |SbInputEventType| values. typedef enum SbInputDeviceType { - // Input from some gesture-detection mechanism. Examples include Kinect, + // Input from a gesture-detection mechanism. Examples include Kinect, // Wiimotes, LG Magic Remotes, etc... // - // Produces Move, Grab, Ungrab, Press and Unpress events. + // Produces |Move|, |Grab|, |Ungrab|, |Press| and |Unpress| events. kSbInputDeviceTypeGesture, // Input from a gamepad, following the layout provided in the W3C Web Gamepad - // API. + // API. [https://www.w3.org/TR/gamepad/] // - // Produces Move, Press and Unpress events. + // Produces |Move|, |Press| and |Unpress| events. kSbInputDeviceTypeGamepad, - // Keyboard input from a traditional keyboard, or game controller chatpad. + // Keyboard input from a traditional keyboard or game controller chatpad. // - // Produces Press and Unpress events. + // Produces |Press| and |Unpress| events. kSbInputDeviceTypeKeyboard, // Input from a microphone that would provide audio data to the caller, who // may then find some way to detect speech or other sounds within it. It may // have processed or filtered the audio in some way before it arrives. // - // Produces Audio events. + // Produces |Audio| events. kSbInputDeviceTypeMicrophone, // Input from a traditional mouse. // - // Produces Move, Press, and Unpress events. + // Produces |Move|, |Press|, and |Unpress| events. kSbInputDeviceTypeMouse, - // Input from a TV remote control-style device. + // Input from a TV remote-control-style device. // - // Produces Press and Unpress events. + // Produces |Press| and |Unpress| events. kSbInputDeviceTypeRemote, // Input from a speech command analyzer, which is some hardware or software // that, given a set of known phrases, activates when one of the registered // phrases is heard. // - // Produces Command events. + // Produces |Command| events. kSbInputDeviceTypeSpeechCommand, // Input from a single- or multi-touchscreen. // - // Produces Move, Press, and Unpress events. + // Produces |Move|, |Press|, and |Unpress| events. kSbInputDeviceTypeTouchScreen, - // Input from a touchpad that isn't masquerading as a mouse. + // Input from a touchpad that is not masquerading as a mouse. // - // Produces Move, Press, and Unpress events. + // Produces |Move|, |Press|, and |Unpress| events. kSbInputDeviceTypeTouchPad, -} SbInputType; +} SbInputDeviceType; // The action that an input event represents. typedef enum SbInputEventType { @@ -90,28 +93,26 @@ // like a speech recognizer. kSbInputEventTypeCommand, - // Grab activation. In some gesture systems a grab gesture is dfferent from an - // activate gesture. An Ungrab event will follow when the grab gesture is - // terminated. + // Grab activation. This event type is deprecated. kSbInputEventTypeGrab, - // Device Movement. In the case of Mouse, and perhaps Gesture, the movement - // tracks an absolute cursor position. In the case of TouchPad, only relative - // movements are provided. + // Device Movement. In the case of |Mouse|, and perhaps |Gesture|, the + // movement tracks an absolute cursor position. In the case of |TouchPad|, + // only relative movements are provided. kSbInputEventTypeMove, - // Key or button press activation. This could be a key on a keyboard, a button - // on a mouse or game controller, or a push from a touch screen or gesture. An - // Unpress event will be delivered once the key/button/finger is - // raised. Injecting repeat presses is up to the client. + // Key or button press activation. This could be a key on a keyboard, a + // button on a mouse or game controller, a push from a touch screen, or + // a gesture. An |Unpress| event is subsequently delivered when the + // |Press| event terminates, such as when the key/button/finger is raised. + // Injecting repeat presses is up to the client. kSbInputEventTypePress, - // Grab deactivation. An Ungrab event will be sent when a grab gesture is - // terminated. + // Grab deactivation. This event type is deprecated. kSbInputEventTypeUngrab, - // Key or button deactivation. The counterpart to the Press event, this event - // is sent when the key or button being pressed is released. + // Key or button deactivation. The counterpart to the |Press| event, this + // event is sent when the key or button being pressed is released. kSbInputEventTypeUnpress, } SbInputEventType; @@ -121,42 +122,44 @@ int y; } SbInputVector; -// Event data for kSbEventTypeInput events. +// Event data for |kSbEventTypeInput| events. typedef struct SbInputData { - // The window that this input was generated at. + // The window in which the input was generated. SbWindow window; - // The type of input event this represents. + // The type of input event that this represents. SbInputEventType type; // The type of device that generated this input event. SbInputDeviceType device_type; - // An identifier unique amongst all connected devices. + // An identifier that is unique among all connected devices. int device_id; // An identifier that indicates which keyboard key or mouse button was // involved in this event, if any. All known keys for all devices are mapped - // to a single ID space, defined by the enum SbKey in key.h. + // to a single ID space, defined by the |SbKey| enum in |key.h|. SbKey key; // The character that corresponds to the key. For an external keyboard, this - // character also depends on the language of keyboard type. Will be 0 if there + // character also depends on the keyboard language. The value is |0| if there // is no corresponding character. wchar_t character; // The location of the specified key, in cases where there are multiple - // instances of the button on the keyboard. The "shift" key, for example. + // instances of the button on the keyboard. For example, some keyboards + // have more than one "shift" key. SbKeyLocation key_location; - // Key modifiers (e.g. Ctrl, Chift) held down during this input event. + // Key modifiers (e.g. |Ctrl|, |Shift|) held down during this input event. unsigned int key_modifiers; // The (x, y) coordinates of the persistent cursor controlled by this - // device. Will be 0 if not applicable. + // device. The value is |0| if this data is not applicable. SbInputVector position; - // The relative motion vector of this input. Will be 0 if not applicable. + // The relative motion vector of this input. The value is |0| if this data + // is not applicable. SbInputVector delta; } SbInputData;
diff --git a/src/starboard/key.h b/src/starboard/key.h index 05f05d9..b50608f 100644 --- a/src/starboard/key.h +++ b/src/starboard/key.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// The canonical set of Starboard key codes. +// Module Overview: Starboard Key module +// +// Defines the canonical set of Starboard key codes. #ifndef STARBOARD_KEY_H_ #define STARBOARD_KEY_H_ @@ -21,17 +23,16 @@ extern "C" { #endif -// A standard set of key codes representing each possible input key across all -// kinds of devices, we use the semi-standard Windows virtual key codes. -// -// Windows virtual key codes doc: +// A standard set of key codes, ordered by value, that represent each possible +// input key across all kinds of devices. Starboard uses the semi-standard +// Windows virtual key codes documented at: // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731%28v=vs.85%29.aspx -// -// The order here is by value. typedef enum SbKey { kSbKeyUnknown = 0, kSbKeyCancel = 0x03, - kSbKeyBack = 0x08, + kSbKeyBackspace = 0x08, + kSbKeyBack = kSbKeyBackspace, // You probably want kSbKeyEscape for a + // semantic "back". kSbKeyTab = 0x09, kSbKeyBacktab = 0x0A, kSbKeyClear = 0x0C,
diff --git a/src/starboard/linux/shared/configuration_public.h b/src/starboard/linux/shared/configuration_public.h index 6d8d416..afd4574 100644 --- a/src/starboard/linux/shared/configuration_public.h +++ b/src/starboard/linux/shared/configuration_public.h
@@ -23,8 +23,9 @@ #ifndef STARBOARD_LINUX_SHARED_CONFIGURATION_PUBLIC_H_ #define STARBOARD_LINUX_SHARED_CONFIGURATION_PUBLIC_H_ -// The API version implemented by this platform. -#define SB_API_VERSION 1 +#ifndef SB_API_VERSION +#define SB_API_VERSION 2 +#endif // --- System Header Configuration ------------------------------------------- @@ -52,6 +53,15 @@ // Whether the current platform provides the standard header limits.h. #define SB_HAS_LIMITS_H 1 +// Whether the current platform provides the standard header float.h. +#define SB_HAS_FLOAT_H 1 + +// Whether the current platform has microphone supported. +#define SB_HAS_MICROPHONE 0 + +// Whether the current platform has speech synthesis. +#define SB_HAS_SPEECH_SYNTHESIS 0 + // Type detection for wchar_t. #if defined(__WCHAR_MAX__) && \ (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff) @@ -69,6 +79,12 @@ #define SB_IS_WCHAR_T_SIGNED 1 #endif +// --- Architecture Configuration -------------------------------------------- + +// On default Linux desktop, you must be a superuser in order to set real time +// scheduling on threads. +#define SB_HAS_THREAD_PRIORITY_SUPPORT 0 + // --- Attribute Configuration ----------------------------------------------- // The platform's annotation for forcing a C function to be inlined. @@ -365,6 +381,12 @@ // The maximum number of users that can be signed in at the same time. #define SB_USER_MAX_SIGNED_IN 1 +// --- Timing API ------------------------------------------------------------ + +// Whether this platform has an API to retrieve how long the current thread +// has spent in the executing state. +#define SB_HAS_TIME_THREAD_NOW 1 + // --- Platform Specific Audits ---------------------------------------------- #if !defined(__GNUC__)
diff --git a/src/starboard/linux/shared/gyp_configuration.gypi b/src/starboard/linux/shared/gyp_configuration.gypi index 703b774..af152fe 100644 --- a/src/starboard/linux/shared/gyp_configuration.gypi +++ b/src/starboard/linux/shared/gyp_configuration.gypi
@@ -21,15 +21,13 @@ 'target_os': 'linux', #'starboard_path%': 'starboard/linux/x64x11', - 'enable_webdriver': '1', 'in_app_dial%': 1, 'gl_type%': 'system_gles3', # This should have a default value in cobalt/base.gypi. See the comment # there for acceptable values for this variable. 'javascript_engine': 'mozjs', - # JIT is temporarily disabled for spidermonkey. - 'cobalt_enable_jit': 0, + 'cobalt_enable_jit': 1, # Define platform specific compiler and linker flags. # Refer to base.gypi for a list of all available variables.
diff --git a/src/starboard/linux/shared/gyp_configuration.py b/src/starboard/linux/shared/gyp_configuration.py index cb6edb4..0baa281 100644 --- a/src/starboard/linux/shared/gyp_configuration.py +++ b/src/starboard/linux/shared/gyp_configuration.py
@@ -49,10 +49,18 @@ # set to 1 in the environment. use_asan_default = self.asan_default if not use_tsan and configuration in ( Configs.DEBUG, Configs.DEVEL) else 0 + + # Set environmental variable to enable_mtm: 'USE_MTM' + # Terminal: `export {varname}={value}` + # Note: must also edit gyp_configuration.gypi per internal instructions. + mtm_on_by_default = 0 + mtm_enabled = int(os.environ.get('USE_MTM', mtm_on_by_default)) + variables.update({ 'clang': 1, 'use_asan': int(os.environ.get('USE_ASAN', use_asan_default)), 'use_tsan': use_tsan, + 'enable_mtm': mtm_enabled }) if variables.get('use_asan') == 1 and variables.get('use_tsan') == 1:
diff --git a/src/starboard/linux/shared/starboard_base_symbolize.gyp b/src/starboard/linux/shared/starboard_base_symbolize.gyp new file mode 100644 index 0000000..5cd3440 --- /dev/null +++ b/src/starboard/linux/shared/starboard_base_symbolize.gyp
@@ -0,0 +1,25 @@ +# Copyright 2016 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. +{ + 'targets': [ + { + 'target_name': 'starboard_base_symbolize', + 'type': 'static_library', + 'sources': [ + '<(DEPTH)/base/third_party/symbolize/demangle.cc', + '<(DEPTH)/base/third_party/symbolize/symbolize.cc', + ], + }, + ], +}
diff --git a/src/starboard/linux/shared/starboard_platform.gypi b/src/starboard/linux/shared/starboard_platform.gypi new file mode 100644 index 0000000..ff6149d --- /dev/null +++ b/src/starboard/linux/shared/starboard_platform.gypi
@@ -0,0 +1,282 @@ +# Copyright 2016 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. +{ + 'variables': { + 'starboard_platform_sources': [ + '<(DEPTH)/starboard/linux/shared/atomic_public.h', + '<(DEPTH)/starboard/linux/shared/configuration_public.h', + '<(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', + '<(DEPTH)/starboard/linux/shared/system_has_capability.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h', + '<(DEPTH)/starboard/shared/alsa/alsa_util.cc', + '<(DEPTH)/starboard/shared/alsa/alsa_util.h', + '<(DEPTH)/starboard/shared/alsa/audio_sink_get_max_channels.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_get_nearest_supported_sample_frequency.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_frame_storage_type_supported.cc', + '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_sample_type_supported.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_aligned_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_free.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc', + '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc', + '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h', + '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h', + '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc', + '<(DEPTH)/starboard/shared/iso/character_is_digit.cc', + '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc', + '<(DEPTH)/starboard/shared/iso/character_is_space.cc', + '<(DEPTH)/starboard/shared/iso/character_is_upper.cc', + '<(DEPTH)/starboard/shared/iso/character_to_lower.cc', + '<(DEPTH)/starboard/shared/iso/character_to_upper.cc', + '<(DEPTH)/starboard/shared/iso/directory_close.cc', + '<(DEPTH)/starboard/shared/iso/directory_get_next.cc', + '<(DEPTH)/starboard/shared/iso/directory_open.cc', + '<(DEPTH)/starboard/shared/iso/double_absolute.cc', + '<(DEPTH)/starboard/shared/iso/double_exponent.cc', + '<(DEPTH)/starboard/shared/iso/double_floor.cc', + '<(DEPTH)/starboard/shared/iso/double_is_finite.cc', + '<(DEPTH)/starboard/shared/iso/double_is_nan.cc', + '<(DEPTH)/starboard/shared/iso/memory_compare.cc', + '<(DEPTH)/starboard/shared/iso/memory_copy.cc', + '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc', + '<(DEPTH)/starboard/shared/iso/memory_move.cc', + '<(DEPTH)/starboard/shared/iso/memory_set.cc', + '<(DEPTH)/starboard/shared/iso/string_compare.cc', + '<(DEPTH)/starboard/shared/iso/string_compare_all.cc', + '<(DEPTH)/starboard/shared/iso/string_find_character.cc', + '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc', + '<(DEPTH)/starboard/shared/iso/string_find_string.cc', + '<(DEPTH)/starboard/shared/iso/string_get_length.cc', + '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_double.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc', + '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc', + '<(DEPTH)/starboard/shared/iso/string_scan.cc', + '<(DEPTH)/starboard/shared/iso/system_binary_search.cc', + '<(DEPTH)/starboard/shared/iso/system_sort.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc', + '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc', + '<(DEPTH)/starboard/shared/linux/byte_swap.cc', + '<(DEPTH)/starboard/shared/linux/get_home_directory.cc', + '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc', + '<(DEPTH)/starboard/shared/linux/page_internal.cc', + '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc', + '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc', + '<(DEPTH)/starboard/shared/linux/system_get_stack.cc', + '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc', + '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc', + '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc', + '<(DEPTH)/starboard/shared/linux/system_symbolize.cc', + '<(DEPTH)/starboard/shared/linux/thread_get_id.cc', + '<(DEPTH)/starboard/shared/linux/thread_get_name.cc', + '<(DEPTH)/starboard/shared/linux/thread_set_name.cc', + '<(DEPTH)/starboard/shared/nouser/user_get_current.cc', + '<(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/posix/directory_create.cc', + '<(DEPTH)/starboard/shared/posix/file_can_open.cc', + '<(DEPTH)/starboard/shared/posix/file_close.cc', + '<(DEPTH)/starboard/shared/posix/file_delete.cc', + '<(DEPTH)/starboard/shared/posix/file_exists.cc', + '<(DEPTH)/starboard/shared/posix/file_flush.cc', + '<(DEPTH)/starboard/shared/posix/file_get_info.cc', + '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc', + '<(DEPTH)/starboard/shared/posix/file_open.cc', + '<(DEPTH)/starboard/shared/posix/file_read.cc', + '<(DEPTH)/starboard/shared/posix/file_seek.cc', + '<(DEPTH)/starboard/shared/posix/file_truncate.cc', + '<(DEPTH)/starboard/shared/posix/file_write.cc', + '<(DEPTH)/starboard/shared/posix/log.cc', + '<(DEPTH)/starboard/shared/posix/log_flush.cc', + '<(DEPTH)/starboard/shared/posix/log_format.cc', + '<(DEPTH)/starboard/shared/posix/log_is_tty.cc', + '<(DEPTH)/starboard/shared/posix/log_raw.cc', + '<(DEPTH)/starboard/shared/posix/memory_flush.cc', + '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc', + '<(DEPTH)/starboard/shared/posix/socket_accept.cc', + '<(DEPTH)/starboard/shared/posix/socket_bind.cc', + '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc', + '<(DEPTH)/starboard/shared/posix/socket_connect.cc', + '<(DEPTH)/starboard/shared/posix/socket_create.cc', + '<(DEPTH)/starboard/shared/posix/socket_destroy.cc', + '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc', + '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc', + '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc', + '<(DEPTH)/starboard/shared/posix/socket_internal.cc', + '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc', + '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc', + '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc', + '<(DEPTH)/starboard/shared/posix/socket_listen.cc', + '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc', + '<(DEPTH)/starboard/shared/posix/socket_resolve.cc', + '<(DEPTH)/starboard/shared/posix/socket_send_to.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc', + '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc', + '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc', + '<(DEPTH)/starboard/shared/posix/string_format.cc', + '<(DEPTH)/starboard/shared/posix/string_format_wide.cc', + '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc', + '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc', + '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc', + '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc', + '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc', + '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc', + '<(DEPTH)/starboard/shared/posix/thread_sleep.cc', + '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc', + '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc', + '<(DEPTH)/starboard/shared/posix/time_get_now.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc', + '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc', + '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_create.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc', + '<(DEPTH)/starboard/shared/pthread/mutex_release.cc', + '<(DEPTH)/starboard/shared/pthread/once.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create_priority.h', + '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc', + '<(DEPTH)/starboard/shared/pthread/thread_detach.cc', + '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc', + '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc', + '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc', + '<(DEPTH)/starboard/shared/pthread/thread_join.cc', + '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc', + '<(DEPTH)/starboard/shared/pthread/thread_yield.cc', + '<(DEPTH)/starboard/shared/signal/crash_signals.h', + '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc', + '<(DEPTH)/starboard/shared/signal/suspend_signals.cc', + '<(DEPTH)/starboard/shared/signal/suspend_signals.h', + '<(DEPTH)/starboard/shared/starboard/application.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h', + '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_is_valid.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc', + '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.h', + '<(DEPTH)/starboard/shared/starboard/directory_can_open.cc', + '<(DEPTH)/starboard/shared/starboard/event_cancel.cc', + '<(DEPTH)/starboard/shared/starboard/event_schedule.cc', + '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc', + '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc', + '<(DEPTH)/starboard/shared/starboard/log_message.cc', + '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc', + '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc', + '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc', + '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc', + '<(DEPTH)/starboard/shared/starboard/media/mime_type.h', + '<(DEPTH)/starboard/shared/starboard/new.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/player_create.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_worker.h', + '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc', + '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc', + '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h', + '<(DEPTH)/starboard/shared/starboard/queue_application.cc', + '<(DEPTH)/starboard/shared/starboard/string_concat.cc', + '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc', + '<(DEPTH)/starboard/shared/starboard/string_copy.cc', + '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc', + '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc', + '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc', + '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc', + '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc', + '<(DEPTH)/starboard/shared/stub/drm_close_session.cc', + '<(DEPTH)/starboard/shared/stub/drm_create_system.cc', + '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc', + '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc', + '<(DEPTH)/starboard/shared/stub/drm_system_internal.h', + '<(DEPTH)/starboard/shared/stub/drm_update_session.cc', + '<(DEPTH)/starboard/shared/stub/media_is_supported.cc', + '<(DEPTH)/starboard/shared/stub/microphone_close.cc', + '<(DEPTH)/starboard/shared/stub/microphone_create.cc', + '<(DEPTH)/starboard/shared/stub/microphone_destroy.cc', + '<(DEPTH)/starboard/shared/stub/microphone_get_available.cc', + '<(DEPTH)/starboard/shared/stub/microphone_is_sample_rate_supported.cc', + '<(DEPTH)/starboard/shared/stub/microphone_open.cc', + '<(DEPTH)/starboard/shared/stub/microphone_read.cc', + '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc', + '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc', + '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc', + '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc', + '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc', + ], + 'starboard_platform_dependencies': [ + '<(DEPTH)/starboard/common/common.gyp:common', + '<(DEPTH)/starboard/linux/shared/starboard_base_symbolize.gyp:starboard_base_symbolize', + '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc', + '<(DEPTH)/third_party/libevent/libevent.gyp:libevent', + ], + }, +}
diff --git a/src/starboard/linux/x64directfb/configuration_public.h b/src/starboard/linux/x64directfb/configuration_public.h index c891232..6f91914 100644 --- a/src/starboard/linux/x64directfb/configuration_public.h +++ b/src/starboard/linux/x64directfb/configuration_public.h
@@ -52,4 +52,8 @@ #define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_BGRA #endif +// The current platform has microphone supported. +#undef SB_HAS_MICROPHONE +#define SB_HAS_MICROPHONE 1 + #endif // STARBOARD_LINUX_X64DIRECTFB_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/future/atomic_public.h b/src/starboard/linux/x64directfb/future/atomic_public.h new file mode 100644 index 0000000..e4ef8fd --- /dev/null +++ b/src/starboard/linux/x64directfb/future/atomic_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_LINUX_X64DIRECTFB_FUTURE_ATOMIC_PUBLIC_H_ +#define STARBOARD_LINUX_X64DIRECTFB_FUTURE_ATOMIC_PUBLIC_H_ + +#include "starboard/linux/shared/atomic_public.h" + +#endif // STARBOARD_LINUX_X64DIRECTFB_FUTURE_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/future/configuration_public.h b/src/starboard/linux/x64directfb/future/configuration_public.h new file mode 100644 index 0000000..db7ef6e --- /dev/null +++ b/src/starboard/linux/x64directfb/future/configuration_public.h
@@ -0,0 +1,28 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The Starboard configuration for Desktop X64 Linux configured for the future +// starboard API. + +#ifndef STARBOARD_LINUX_X64DIRECTFB_FUTURE_CONFIGURATION_PUBLIC_H_ +#define STARBOARD_LINUX_X64DIRECTFB_FUTURE_CONFIGURATION_PUBLIC_H_ + +// Include the X64DIRECTFB Linux configuration. +#include "starboard/linux/x64directfb/configuration_public.h" + +// The API version implemented by this platform. +#undef SB_API_VERSION +#define SB_API_VERSION SB_EXPERIMENTAL_API_VERSION + +#endif // STARBOARD_LINUX_X64DIRECTFB_FUTURE_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/future/gyp_configuration.gypi b/src/starboard/linux/x64directfb/future/gyp_configuration.gypi new file mode 100644 index 0000000..44b920d --- /dev/null +++ b/src/starboard/linux/x64directfb/future/gyp_configuration.gypi
@@ -0,0 +1,37 @@ +# Copyright 2016 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. + +{ + 'target_defaults': { + 'default_configuration': 'linux-x64directfb-future_debug', + 'configurations': { + 'linux-x64directfb-future_debug': { + 'inherit_from': ['debug_base'], + }, + 'linux-x64directfb-future_devel': { + 'inherit_from': ['devel_base'], + }, + 'linux-x64directfb-future_qa': { + 'inherit_from': ['qa_base'], + }, + 'linux-x64directfb-future_gold': { + 'inherit_from': ['gold_base'], + }, + }, # end of configurations + }, + + 'includes': [ + '../gyp_configuration.gypi', + ], +}
diff --git a/src/starboard/linux/x64directfb/future/gyp_configuration.py b/src/starboard/linux/x64directfb/future/gyp_configuration.py new file mode 100644 index 0000000..2f7fe1b --- /dev/null +++ b/src/starboard/linux/x64directfb/future/gyp_configuration.py
@@ -0,0 +1,33 @@ +# Copyright 2016 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. +"""Starboard Linux X64 DirectFB future platform configuration for gyp_cobalt.""" + +import logging +import os +import sys + +# Import the shared Linux platform configuration. +sys.path.append( + os.path.realpath( + os.path.join( + os.path.dirname(__file__), os.pardir, os.pardir, 'shared'))) +import gyp_configuration + + +def CreatePlatformConfig(): + try: + return gyp_configuration.PlatformConfig('linux-x64directfb-future') + except RuntimeError as e: + logging.critical(e) + return None
diff --git a/src/starboard/linux/x64directfb/future/starboard_platform.gyp b/src/starboard/linux/x64directfb/future/starboard_platform.gyp new file mode 100644 index 0000000..94f15db --- /dev/null +++ b/src/starboard/linux/x64directfb/future/starboard_platform.gyp
@@ -0,0 +1,42 @@ +# Copyright 2016 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. +{ + 'targets': [ + { + 'target_name': 'starboard_platform', + 'product_name': 'starboard_platform_future', + 'type': 'static_library', + 'sources': [ + '<(DEPTH)/starboard/shared/stub/decode_target_create_blitter.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_destroy.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_get_format.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_get_plane_blitter.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_is_opaque.cc', + '<(DEPTH)/starboard/shared/stub/image_decode.cc', + '<(DEPTH)/starboard/shared/stub/image_is_decode_supported.cc', + ], + 'defines': [ + # This must be defined when building Starboard, and must not when + # building Starboard client code. + 'STARBOARD_IMPLEMENTATION', + ], + 'dependencies': [ + '<(DEPTH)/starboard/common/common.gyp:common', + '<(DEPTH)/starboard/linux/x64directfb/starboard_platform.gyp:starboard_platform', + '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc', + '<(DEPTH)/third_party/libevent/libevent.gyp:libevent', + ], + }, + ], +}
diff --git a/src/starboard/linux/x64directfb/future/thread_types_public.h b/src/starboard/linux/x64directfb/future/thread_types_public.h new file mode 100644 index 0000000..19803be --- /dev/null +++ b/src/starboard/linux/x64directfb/future/thread_types_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_LINUX_X64DIRECTFB_FUTURE_THREAD_TYPES_PUBLIC_H_ +#define STARBOARD_LINUX_X64DIRECTFB_FUTURE_THREAD_TYPES_PUBLIC_H_ + +#include "starboard/linux/shared/thread_types_public.h" + +#endif // STARBOARD_LINUX_X64DIRECTFB_FUTURE_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/linux/x64directfb/sanitizer_options.cc b/src/starboard/linux/x64directfb/sanitizer_options.cc new file mode 100644 index 0000000..e85d451 --- /dev/null +++ b/src/starboard/linux/x64directfb/sanitizer_options.cc
@@ -0,0 +1,42 @@ +// Copyright 2016 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. + +// Removes gallium leak warnings from x11 GL code, but defines it as a weak +// symbol, so other code can override it if they want to. + +#if defined(ADDRESS_SANITIZER) + +// Functions returning default options are declared weak in the tools' runtime +// libraries. To make the linker pick the strong replacements for those +// functions from this module, we explicitly force its inclusion by passing +// -Wl,-u_sanitizer_options_link_helper +extern "C" void _sanitizer_options_link_helper() { } + +#define SANITIZER_HOOK_ATTRIBUTE \ + extern "C" \ + __attribute__((no_sanitize_address)) \ + __attribute__((no_sanitize_memory)) \ + __attribute__((no_sanitize_thread)) \ + __attribute__((visibility("default"))) \ + __attribute__((weak)) \ + __attribute__((used)) + +// Newline separated list of issues to suppress, see +// http://clang.llvm.org/docs/AddressSanitizer.html#issue-suppression +// http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/sanitizer_common/sanitizer_suppressions.cc +SANITIZER_HOOK_ATTRIBUTE const char* __lsan_default_suppressions() { + return "leak:*eglibc-2.19*\n"; +} + +#endif // defined(ADDRESS_SANITIZER)
diff --git a/src/starboard/linux/x64directfb/starboard_platform.gyp b/src/starboard/linux/x64directfb/starboard_platform.gyp index 331fd6c..c45f24a 100644 --- a/src/starboard/linux/x64directfb/starboard_platform.gyp +++ b/src/starboard/linux/x64directfb/starboard_platform.gyp
@@ -12,307 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. { + 'includes': [ + 'starboard_platform.gypi' + ], 'targets': [ { - 'target_name': 'starboard_base_symbolize', - 'type': 'static_library', - 'sources': [ - '<(DEPTH)/base/third_party/symbolize/demangle.cc', - '<(DEPTH)/base/third_party/symbolize/symbolize.cc', - ], - }, - { 'target_name': 'starboard_platform', 'type': 'static_library', - 'sources': [ - '<(DEPTH)/starboard/linux/shared/atomic_public.h', - '<(DEPTH)/starboard/linux/shared/configuration_public.h', - '<(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', - '<(DEPTH)/starboard/linux/shared/system_has_capability.cc', - '<(DEPTH)/starboard/linux/x64directfb/main.cc', - '<(DEPTH)/starboard/linux/x64directfb/system_get_property.cc', - '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.cc', - '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h', - '<(DEPTH)/starboard/shared/alsa/alsa_util.cc', - '<(DEPTH)/starboard/shared/alsa/alsa_util.h', - '<(DEPTH)/starboard/shared/alsa/audio_sink_get_max_channels.cc', - '<(DEPTH)/starboard/shared/alsa/audio_sink_get_nearest_supported_sample_frequency.cc', - '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_frame_storage_type_supported.cc', - '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_sample_type_supported.cc', - '<(DEPTH)/starboard/shared/directfb/application_directfb.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_blit_rect_to_rect.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_create_context.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_create_default_device.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_create_pixel_data.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_create_render_target_surface.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_create_surface_from_pixel_data.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_create_swap_chain_from_window.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_destroy_context.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_destroy_device.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_destroy_pixel_data.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_destroy_surface.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_destroy_swap_chain.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_download_surface_pixels.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_fill_rect.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_flip_swap_chain.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_flush_context.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_get_max_contexts.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_get_pixel_data_pitch_in_bytes.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_get_pixel_data_pointer.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_get_render_target_from_surface.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_get_render_target_from_swap_chain.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_get_surface_info.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_internal.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_is_pixel_format_supported_by_download_surface_pixels.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_is_pixel_format_supported_by_pixel_data.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_is_surface_format_supported_by_render_target_surface.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_set_blending.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_set_color.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_set_modulate_blits_with_color.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_set_render_target.cc', - '<(DEPTH)/starboard/shared/directfb/blitter_set_scissor.cc', - '<(DEPTH)/starboard/shared/directfb/window_create.cc', - '<(DEPTH)/starboard/shared/directfb/window_destroy.cc', - '<(DEPTH)/starboard/shared/directfb/window_get_platform_handle.cc', - '<(DEPTH)/starboard/shared/directfb/window_get_size.cc', - '<(DEPTH)/starboard/shared/directfb/window_internal.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_aligned_unchecked.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_unchecked.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_free.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h', - '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h', - '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc', - '<(DEPTH)/starboard/shared/iso/character_is_digit.cc', - '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc', - '<(DEPTH)/starboard/shared/iso/character_is_space.cc', - '<(DEPTH)/starboard/shared/iso/character_is_upper.cc', - '<(DEPTH)/starboard/shared/iso/character_to_lower.cc', - '<(DEPTH)/starboard/shared/iso/character_to_upper.cc', - '<(DEPTH)/starboard/shared/iso/directory_close.cc', - '<(DEPTH)/starboard/shared/iso/directory_get_next.cc', - '<(DEPTH)/starboard/shared/iso/directory_open.cc', - '<(DEPTH)/starboard/shared/iso/double_absolute.cc', - '<(DEPTH)/starboard/shared/iso/double_exponent.cc', - '<(DEPTH)/starboard/shared/iso/double_floor.cc', - '<(DEPTH)/starboard/shared/iso/double_is_finite.cc', - '<(DEPTH)/starboard/shared/iso/double_is_nan.cc', - '<(DEPTH)/starboard/shared/iso/memory_compare.cc', - '<(DEPTH)/starboard/shared/iso/memory_copy.cc', - '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc', - '<(DEPTH)/starboard/shared/iso/memory_move.cc', - '<(DEPTH)/starboard/shared/iso/memory_set.cc', - '<(DEPTH)/starboard/shared/iso/string_compare.cc', - '<(DEPTH)/starboard/shared/iso/string_compare_all.cc', - '<(DEPTH)/starboard/shared/iso/string_find_character.cc', - '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc', - '<(DEPTH)/starboard/shared/iso/string_find_string.cc', - '<(DEPTH)/starboard/shared/iso/string_get_length.cc', - '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc', - '<(DEPTH)/starboard/shared/iso/string_parse_double.cc', - '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc', - '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc', - '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc', - '<(DEPTH)/starboard/shared/iso/string_scan.cc', - '<(DEPTH)/starboard/shared/iso/system_binary_search.cc', - '<(DEPTH)/starboard/shared/iso/system_sort.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc', - '<(DEPTH)/starboard/shared/linux/byte_swap.cc', - '<(DEPTH)/starboard/shared/linux/get_home_directory.cc', - '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc', - '<(DEPTH)/starboard/shared/linux/page_internal.cc', - '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc', - '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc', - '<(DEPTH)/starboard/shared/linux/system_get_stack.cc', - '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc', - '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc', - '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc', - '<(DEPTH)/starboard/shared/linux/system_symbolize.cc', - '<(DEPTH)/starboard/shared/linux/thread_get_id.cc', - '<(DEPTH)/starboard/shared/linux/thread_get_name.cc', - '<(DEPTH)/starboard/shared/linux/thread_set_name.cc', - '<(DEPTH)/starboard/shared/nouser/user_get_current.cc', - '<(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/posix/directory_create.cc', - '<(DEPTH)/starboard/shared/posix/file_can_open.cc', - '<(DEPTH)/starboard/shared/posix/file_close.cc', - '<(DEPTH)/starboard/shared/posix/file_delete.cc', - '<(DEPTH)/starboard/shared/posix/file_exists.cc', - '<(DEPTH)/starboard/shared/posix/file_flush.cc', - '<(DEPTH)/starboard/shared/posix/file_get_info.cc', - '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc', - '<(DEPTH)/starboard/shared/posix/file_open.cc', - '<(DEPTH)/starboard/shared/posix/file_read.cc', - '<(DEPTH)/starboard/shared/posix/file_seek.cc', - '<(DEPTH)/starboard/shared/posix/file_truncate.cc', - '<(DEPTH)/starboard/shared/posix/file_write.cc', - '<(DEPTH)/starboard/shared/posix/log.cc', - '<(DEPTH)/starboard/shared/posix/log_flush.cc', - '<(DEPTH)/starboard/shared/posix/log_format.cc', - '<(DEPTH)/starboard/shared/posix/log_is_tty.cc', - '<(DEPTH)/starboard/shared/posix/log_raw.cc', - '<(DEPTH)/starboard/shared/posix/memory_flush.cc', - '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc', - '<(DEPTH)/starboard/shared/posix/socket_accept.cc', - '<(DEPTH)/starboard/shared/posix/socket_bind.cc', - '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc', - '<(DEPTH)/starboard/shared/posix/socket_connect.cc', - '<(DEPTH)/starboard/shared/posix/socket_create.cc', - '<(DEPTH)/starboard/shared/posix/socket_destroy.cc', - '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc', - '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc', - '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc', - '<(DEPTH)/starboard/shared/posix/socket_internal.cc', - '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc', - '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc', - '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc', - '<(DEPTH)/starboard/shared/posix/socket_listen.cc', - '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc', - '<(DEPTH)/starboard/shared/posix/socket_resolve.cc', - '<(DEPTH)/starboard/shared/posix/socket_send_to.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc', - '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc', - '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc', - '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc', - '<(DEPTH)/starboard/shared/posix/string_format.cc', - '<(DEPTH)/starboard/shared/posix/string_format_wide.cc', - '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc', - '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc', - '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc', - '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc', - '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc', - '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc', - '<(DEPTH)/starboard/shared/posix/thread_sleep.cc', - '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc', - '<(DEPTH)/starboard/shared/posix/time_get_now.cc', - '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc', - '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc', - '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_create.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_release.cc', - '<(DEPTH)/starboard/shared/pthread/once.cc', - '<(DEPTH)/starboard/shared/pthread/thread_create.cc', - '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc', - '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc', - '<(DEPTH)/starboard/shared/pthread/thread_detach.cc', - '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc', - '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc', - '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc', - '<(DEPTH)/starboard/shared/pthread/thread_join.cc', - '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc', - '<(DEPTH)/starboard/shared/pthread/thread_yield.cc', - '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc', - '<(DEPTH)/starboard/shared/signal/crash_signals.h', - '<(DEPTH)/starboard/shared/signal/suspend_signals.cc', - '<(DEPTH)/starboard/shared/signal/suspend_signals.h', - '<(DEPTH)/starboard/shared/starboard/application.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_is_valid.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.h', - '<(DEPTH)/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc', - '<(DEPTH)/starboard/shared/starboard/blitter_blit_rects_to_rects.cc', - '<(DEPTH)/starboard/shared/starboard/directory_can_open.cc', - '<(DEPTH)/starboard/shared/starboard/event_cancel.cc', - '<(DEPTH)/starboard/shared/starboard/event_schedule.cc', - '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc', - '<(DEPTH)/starboard/shared/starboard/log_message.cc', - '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc', - '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc', - '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc', - '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc', - '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc', - '<(DEPTH)/starboard/shared/starboard/media/mime_parser.cc', - '<(DEPTH)/starboard/shared/starboard/media/mime_parser.h', - '<(DEPTH)/starboard/shared/starboard/new.cc', - '<(DEPTH)/starboard/shared/starboard/player/audio_decoder_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/player_create.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_worker.h', - '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc', - '<(DEPTH)/starboard/shared/starboard/player/video_decoder_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.h', - '<(DEPTH)/starboard/shared/starboard/queue_application.cc', - '<(DEPTH)/starboard/shared/starboard/string_concat.cc', - '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc', - '<(DEPTH)/starboard/shared/starboard/string_copy.cc', - '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc', - '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc', - '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc', - '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc', - '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc', - '<(DEPTH)/starboard/shared/stub/drm_close_session.cc', - '<(DEPTH)/starboard/shared/stub/drm_create_system.cc', - '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc', - '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc', - '<(DEPTH)/starboard/shared/stub/drm_system_internal.h', - '<(DEPTH)/starboard/shared/stub/drm_update_session.cc', - '<(DEPTH)/starboard/shared/stub/media_is_supported.cc', - '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc', - '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc', - '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc', - '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc', - '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc', - ], + 'sources': ['<@(starboard_platform_sources)'], 'include_dirs': [ '/usr/include/directfb', ], @@ -322,9 +29,7 @@ 'STARBOARD_IMPLEMENTATION', ], 'dependencies': [ - '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc', - '<(DEPTH)/third_party/libevent/libevent.gyp:libevent', - 'starboard_base_symbolize', + '<@(starboard_platform_dependencies)', ], }, ],
diff --git a/src/starboard/linux/x64directfb/starboard_platform.gypi b/src/starboard/linux/x64directfb/starboard_platform.gypi new file mode 100644 index 0000000..d38e23c --- /dev/null +++ b/src/starboard/linux/x64directfb/starboard_platform.gypi
@@ -0,0 +1,63 @@ +# Copyright 2016 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. +{ + 'includes': ['../shared/starboard_platform.gypi'], + + 'variables': { + 'starboard_platform_sources': [ + '<(DEPTH)/starboard/linux/x64directfb/main.cc', + '<(DEPTH)/starboard/linux/x64directfb/sanitizer_options.cc', + '<(DEPTH)/starboard/linux/x64directfb/system_get_property.cc', + '<(DEPTH)/starboard/shared/directfb/application_directfb.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_blit_rect_to_rect.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_context.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_default_device.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_pixel_data.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_render_target_surface.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_surface_from_pixel_data.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_create_swap_chain_from_window.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_context.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_device.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_pixel_data.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_surface.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_destroy_swap_chain.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_download_surface_pixels.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_fill_rect.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_flip_swap_chain.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_flush_context.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_max_contexts.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_pixel_data_pitch_in_bytes.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_pixel_data_pointer.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_render_target_from_surface.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_render_target_from_swap_chain.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_get_surface_info.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_internal.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_is_pixel_format_supported_by_download_surface_pixels.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_is_pixel_format_supported_by_pixel_data.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_is_surface_format_supported_by_render_target_surface.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_blending.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_color.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_modulate_blits_with_color.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_render_target.cc', + '<(DEPTH)/starboard/shared/directfb/blitter_set_scissor.cc', + '<(DEPTH)/starboard/shared/directfb/window_create.cc', + '<(DEPTH)/starboard/shared/directfb/window_destroy.cc', + '<(DEPTH)/starboard/shared/directfb/window_get_platform_handle.cc', + '<(DEPTH)/starboard/shared/directfb/window_get_size.cc', + '<(DEPTH)/starboard/shared/directfb/window_internal.cc', + '<(DEPTH)/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc', + '<(DEPTH)/starboard/shared/starboard/blitter_blit_rects_to_rects.cc', + ], + }, +}
diff --git a/src/starboard/linux/x64directfb/system_get_property.cc b/src/starboard/linux/x64directfb/system_get_property.cc index b1529fe..1d20f7f 100644 --- a/src/starboard/linux/x64directfb/system_get_property.cc +++ b/src/starboard/linux/x64directfb/system_get_property.cc
@@ -47,6 +47,7 @@ case kSbSystemPropertyModelName: case kSbSystemPropertyModelYear: case kSbSystemPropertyNetworkOperatorName: + case kSbSystemPropertySpeechApiKey: return false; case kSbSystemPropertyFriendlyName:
diff --git a/src/starboard/linux/x64directfb/xephyr_run.sh b/src/starboard/linux/x64directfb/xephyr_run.sh index dc3181c..77bf969 100755 --- a/src/starboard/linux/x64directfb/xephyr_run.sh +++ b/src/starboard/linux/x64directfb/xephyr_run.sh
@@ -13,44 +13,123 @@ # See the License for the specific language governing permissions and # limitations under the License. -# This script can be used as a convenience to launch an app under a Xephyr -# X server. It will first launch Xephyr, and then launch the executable given -# on the command line such that it targets and will appear within the Xephyr -# window. It shuts down Xephyr after the main executable finishes. +# This script can be used as a convenience to launch an app under a new +# X server. Xephyr requires a base X server, so will be launched if there is a +# DISPLAY environment variable set. Xvfb does not, so it will be launched if +# there is no DISPLAY variable set, which is likely to come from a remote shell +# or background process. It will first launch the X server, and then launch the +# executable given on the command line such that it targets the newly-launched +# X server. It shuts down the X server after the main executable finishes. -if [[ $# = 0 ]]; then - echo - echo "$0 will launch a given executable file under a Xephyr X server." - echo - echo "Usage:" - echo " $0 PATH_TO_EXECUTABLE_FILE " - echo - exit 0 +# Standard stuff to get the real script name. +script_file="$(readlink -f "${BASH_SOURCE[0]}")" +script_name="$(basename "${script_file}")" + +if [[ -n "$DISPLAY" ]]; then + # The binary to run to start the X Server. + xserver_bin=Xephyr + # The command-line args to set the screen dimensions. + declare -a xserver_screen=(-screen 1920x1080) + # The package to install for this X Server. + xserver_package=xserver-xephyr +else + xserver_bin=Xvfb + declare -a xserver_screen=(-screen 0 1920x1080x24) + xserver_package=xvfb fi -if ! hash Xephyr 2>/dev/null ; then - echo - echo "Xephyr is not installed. Please run:" - echo " sudo apt-get install xserver-xephyr" - echo - exit 0 -fi +# Use display 42 as it is unlikely to be used by another process on this host. +# TODO: Scan displays for a free one. +xserver_display=":42" -# Open up a Xephyr display. -Xephyr -screen 1920x1080 :10 2> /dev/null & -xephyr_pid=$! +function log() { + echo "${script_name}: $@" +} -# Setup a trap to clean up Xephyr upon termination of this script -function kill1() { +function deleteTempTrap() { + # If something interrupted the script, it might have printed a partial line. + echo + deleteDirectory "${temp_dir}" +} + +function killServerTrap() { + echo # Kill the Xephyr process. - kill $xephyr_pid 2> /dev/null + kill "${xserver_pid}" &> /dev/null # Waits for the kill to finish. wait + log "${xserver_bin} (pid ${xserver_pid}) terminated." + deleteDirectory "${temp_dir}" } -trap kill1 EXIT -# Give Xephyr some time to setup. -sleep 0.5 +function deleteDirectory() { + if [[ -z "$1" ]]; then + log "deleteDirectory with no argument" + exit 1 + fi -# Launch the executable passed as a parameter on the Xephyr display. -DISPLAY=:10 $@ \ No newline at end of file + # Only delete target if it is an existing directory. + if [[ -d "$1" ]]; then + rm -rf "$1" + fi +} + +function main() { + if [[ "$#" = "0" ]]; then + echo + echo "${script_name}: Launches a given executable file under a ${xserver_bin} X server." + echo + echo "Usage:" + echo " ${script_name} PATH_TO_EXECUTABLE_FILE [ARGS...] " + echo + exit 1 + fi + + if ! hash "${xserver_bin}" 2>/dev/null ; then + log "${xserver_bin} is not installed. Please run:" + log " sudo apt-get install ${xserver_package}" + exit 1 + fi + + # Create an auth file that will allow the current user to access the display. + temp_dir="$(mktemp -dt "${script_name}.XXXXXXXXXX")" + + # Delete the temporary directory on exit. + trap deleteTempTrap EXIT + + # In this case, we don't want to try to tunnel authority through to remote + # connections, we want to use this X server with the client. So we create + # our own auth and forcibly pass it through both sides of the client and + # server. Apologies for the X11 magic here. + xserver_auth="${temp_dir}/XAuthority" + touch "${xserver_auth}" + token="$(dd if=/dev/urandom count=1 2> /dev/null | md5sum | cut -f1 -d' ')" + xauth -qf "${xserver_auth}" add "${xserver_display}" . "${token}" + + xserver_log="${temp_dir}/${xserver_bin}.txt" + # Launch an X Server in the background at a new display. + "${xserver_bin}" "${xserver_display}" \ + -auth "${xserver_auth}" \ + "${xserver_screen[@]}" \ + &> "${xserver_log}" & + xserver_pid=$! + log "${xserver_bin} (pid ${xserver_pid}) running on ${xserver_display}." + + # Setup a trap to clean up Xephyr upon termination of this script + trap killServerTrap EXIT + + # Give Xephyr some time to setup. + sleep 0.5 + + # Launch the executable passed as a parameter on the new display. + DISPLAY="${xserver_display}" XAUTHORITY="${xserver_auth}" "$@" + result=$? + if [[ "${result}" != "0" ]]; then + log "$1 result: ${result}" + log "--- ${xserver_bin} log ---------------------------------------" + cat "${xserver_log}" + fi + return ${result} +} + +main "$@"
diff --git a/src/starboard/linux/x64x11/configuration_public.h b/src/starboard/linux/x64x11/configuration_public.h index 2291350..8022c88 100644 --- a/src/starboard/linux/x64x11/configuration_public.h +++ b/src/starboard/linux/x64x11/configuration_public.h
@@ -22,6 +22,9 @@ #ifndef STARBOARD_LINUX_X64X11_CONFIGURATION_PUBLIC_H_ #define STARBOARD_LINUX_X64X11_CONFIGURATION_PUBLIC_H_ +// The API version implemented by this platform. +#define SB_API_VERSION 2 + // --- Architecture Configuration -------------------------------------------- // Whether the current platform is big endian. SB_IS_LITTLE_ENDIAN will be @@ -106,7 +109,14 @@ // textures. These textures typically originate from video decoders. #define SB_HAS_NV12_TEXTURE_SUPPORT 1 +// Whether the current platform has speech synthesis. +#define SB_HAS_SPEECH_SYNTHESIS 0 + // Include the Linux configuration that's common between all Desktop Linuxes. #include "starboard/linux/shared/configuration_public.h" +// The current platform has microphone supported. +#undef SB_HAS_MICROPHONE +#define SB_HAS_MICROPHONE 1 + #endif // STARBOARD_LINUX_X64X11_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/future/atomic_public.h b/src/starboard/linux/x64x11/future/atomic_public.h new file mode 100644 index 0000000..e908f7d --- /dev/null +++ b/src/starboard/linux/x64x11/future/atomic_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_LINUX_X64X11_FUTURE_ATOMIC_PUBLIC_H_ +#define STARBOARD_LINUX_X64X11_FUTURE_ATOMIC_PUBLIC_H_ + +#include "starboard/linux/shared/atomic_public.h" + +#endif // STARBOARD_LINUX_X64X11_FUTURE_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/future/configuration_public.h b/src/starboard/linux/x64x11/future/configuration_public.h new file mode 100644 index 0000000..91ab8df --- /dev/null +++ b/src/starboard/linux/x64x11/future/configuration_public.h
@@ -0,0 +1,28 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// The Starboard configuration for Desktop X86 Linux configured for the future +// starboard API. + +#ifndef STARBOARD_LINUX_X64X11_FUTURE_CONFIGURATION_PUBLIC_H_ +#define STARBOARD_LINUX_X64X11_FUTURE_CONFIGURATION_PUBLIC_H_ + +// Include the X64X11 Linux configuration. +#include "starboard/linux/x64x11/configuration_public.h" + +// The API version implemented by this platform. +#undef SB_API_VERSION +#define SB_API_VERSION SB_EXPERIMENTAL_API_VERSION + +#endif // STARBOARD_LINUX_X64X11_FUTURE_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/future/gyp_configuration.gypi b/src/starboard/linux/x64x11/future/gyp_configuration.gypi new file mode 100644 index 0000000..0bd1f09 --- /dev/null +++ b/src/starboard/linux/x64x11/future/gyp_configuration.gypi
@@ -0,0 +1,44 @@ +# Copyright 2016 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. + +{ + 'target_defaults': { + # By default, <EGL/eglplatform.h> pulls in some X11 headers that have some + # nasty macros (|Status|, for example) that conflict with Chromium base. + # Since certain Cobalt headers now depend on EGL through SbDecodeTarget, + # we define this macro to configure EGL to not pull in these headers. + 'defines': [ + 'MESA_EGL_NO_X11_HEADERS' + ], + 'default_configuration': 'linux-x64x11-future_debug', + 'configurations': { + 'linux-x64x11-future_debug': { + 'inherit_from': ['debug_base'], + }, + 'linux-x64x11-future_devel': { + 'inherit_from': ['devel_base'], + }, + 'linux-x64x11-future_qa': { + 'inherit_from': ['qa_base'], + }, + 'linux-x64x11-future_gold': { + 'inherit_from': ['gold_base'], + }, + }, # end of configurations + }, + + 'includes': [ + '../gyp_configuration.gypi', + ], +}
diff --git a/src/starboard/linux/x64x11/future/gyp_configuration.py b/src/starboard/linux/x64x11/future/gyp_configuration.py new file mode 100644 index 0000000..5f82ad7 --- /dev/null +++ b/src/starboard/linux/x64x11/future/gyp_configuration.py
@@ -0,0 +1,33 @@ +# Copyright 2016 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. +"""Starboard Linux X64 X11 future platform configuration for gyp_cobalt.""" + +import logging +import os +import sys + +# Import the shared Linux platform configuration. +sys.path.append( + os.path.realpath( + os.path.join( + os.path.dirname(__file__), os.pardir, os.pardir, 'shared'))) +import gyp_configuration + + +def CreatePlatformConfig(): + try: + return gyp_configuration.PlatformConfig('linux-x64x11-future') + except RuntimeError as e: + logging.critical(e) + return None
diff --git a/src/starboard/linux/x64x11/future/starboard_platform.gyp b/src/starboard/linux/x64x11/future/starboard_platform.gyp new file mode 100644 index 0000000..31eaa25 --- /dev/null +++ b/src/starboard/linux/x64x11/future/starboard_platform.gyp
@@ -0,0 +1,43 @@ +# Copyright 2016 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. +{ + 'includes': [ + '../starboard_platform.gypi' + ], + 'targets': [ + { + 'target_name': 'starboard_platform', + 'product_name': 'starboard_platform_future', + 'type': 'static_library', + 'sources': [ + '<@(starboard_platform_sources)', + '<(DEPTH)/starboard/shared/stub/decode_target_create_egl.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_destroy.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_get_format.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_get_plane_egl.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_is_opaque.cc', + '<(DEPTH)/starboard/shared/stub/image_decode.cc', + '<(DEPTH)/starboard/shared/stub/image_is_decode_supported.cc', + ], + 'defines': [ + # This must be defined when building Starboard, and must not when + # building Starboard client code. + 'STARBOARD_IMPLEMENTATION', + ], + 'dependencies': [ + '<@(starboard_platform_dependencies)', + ], + }, + ], +}
diff --git a/src/starboard/linux/x64x11/future/thread_types_public.h b/src/starboard/linux/x64x11/future/thread_types_public.h new file mode 100644 index 0000000..9ae5da8 --- /dev/null +++ b/src/starboard/linux/x64x11/future/thread_types_public.h
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_LINUX_X64X11_FUTURE_THREAD_TYPES_PUBLIC_H_ +#define STARBOARD_LINUX_X64X11_FUTURE_THREAD_TYPES_PUBLIC_H_ + +#include "starboard/linux/shared/thread_types_public.h" + +#endif // STARBOARD_LINUX_X64X11_FUTURE_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/linux/x64x11/main.cc b/src/starboard/linux/x64x11/main.cc index 7feb245..12cbce1 100644 --- a/src/starboard/linux/x64x11/main.cc +++ b/src/starboard/linux/x64x11/main.cc
@@ -19,7 +19,7 @@ #include "starboard/shared/signal/suspend_signals.h" #include "starboard/shared/x11/application_x11.h" -int main(int argc, char** argv) { +extern "C" SB_EXPORT_PLATFORM int main(int argc, char** argv) { tzset(); starboard::shared::signal::InstallCrashSignalHandlers(); starboard::shared::signal::InstallSuspendSignalHandlers();
diff --git a/src/starboard/linux/x64x11/sanitizer_options.cc b/src/starboard/linux/x64x11/sanitizer_options.cc new file mode 100644 index 0000000..2ac2410 --- /dev/null +++ b/src/starboard/linux/x64x11/sanitizer_options.cc
@@ -0,0 +1,42 @@ +// Copyright 2016 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. + +// Removes gallium leak warnings from x11 GL code, but defines it as a weak +// symbol, so other code can override it if they want to. + +#if defined(ADDRESS_SANITIZER) + +// Functions returning default options are declared weak in the tools' runtime +// libraries. To make the linker pick the strong replacements for those +// functions from this module, we explicitly force its inclusion by passing +// -Wl,-u_sanitizer_options_link_helper +extern "C" void _sanitizer_options_link_helper() { } + +#define SANITIZER_HOOK_ATTRIBUTE \ + extern "C" \ + __attribute__((no_sanitize_address)) \ + __attribute__((no_sanitize_memory)) \ + __attribute__((no_sanitize_thread)) \ + __attribute__((visibility("default"))) \ + __attribute__((weak)) \ + __attribute__((used)) + +// Newline separated list of issues to suppress, see +// http://clang.llvm.org/docs/AddressSanitizer.html#issue-suppression +// http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/sanitizer_common/sanitizer_suppressions.cc +SANITIZER_HOOK_ATTRIBUTE const char* __lsan_default_suppressions() { + return "leak:egl_gallium.so\n"; +} + +#endif // defined(ADDRESS_SANITIZER)
diff --git a/src/starboard/linux/x64x11/starboard_platform.gyp b/src/starboard/linux/x64x11/starboard_platform.gyp index 761a55d..56e5781 100644 --- a/src/starboard/linux/x64x11/starboard_platform.gyp +++ b/src/starboard/linux/x64x11/starboard_platform.gyp
@@ -12,283 +12,21 @@ # See the License for the specific language governing permissions and # limitations under the License. { + 'includes': [ + 'starboard_platform.gypi' + ], 'targets': [ { - 'target_name': 'starboard_base_symbolize', - 'type': 'static_library', - 'sources': [ - '<(DEPTH)/base/third_party/symbolize/demangle.cc', - '<(DEPTH)/base/third_party/symbolize/symbolize.cc', - ], - }, - { 'target_name': 'starboard_platform', 'type': 'static_library', - 'sources': [ - '<(DEPTH)/starboard/linux/shared/atomic_public.h', - '<(DEPTH)/starboard/linux/shared/configuration_public.h', - '<(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', - '<(DEPTH)/starboard/linux/shared/system_has_capability.cc', - '<(DEPTH)/starboard/linux/x64x11/main.cc', - '<(DEPTH)/starboard/linux/x64x11/system_get_property.cc', - '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.cc', - '<(DEPTH)/starboard/shared/alsa/alsa_audio_sink_type.h', - '<(DEPTH)/starboard/shared/alsa/alsa_util.cc', - '<(DEPTH)/starboard/shared/alsa/alsa_util.h', - '<(DEPTH)/starboard/shared/alsa/audio_sink_get_max_channels.cc', - '<(DEPTH)/starboard/shared/alsa/audio_sink_get_nearest_supported_sample_frequency.cc', - '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_frame_storage_type_supported.cc', - '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_sample_type_supported.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_aligned_unchecked.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_unchecked.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_free.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_free_aligned.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_reallocate_unchecked.cc', - '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h', - '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h', - '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc', - '<(DEPTH)/starboard/shared/iso/character_is_digit.cc', - '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc', - '<(DEPTH)/starboard/shared/iso/character_is_space.cc', - '<(DEPTH)/starboard/shared/iso/character_is_upper.cc', - '<(DEPTH)/starboard/shared/iso/character_to_lower.cc', - '<(DEPTH)/starboard/shared/iso/character_to_upper.cc', - '<(DEPTH)/starboard/shared/iso/directory_close.cc', - '<(DEPTH)/starboard/shared/iso/directory_get_next.cc', - '<(DEPTH)/starboard/shared/iso/directory_open.cc', - '<(DEPTH)/starboard/shared/iso/double_absolute.cc', - '<(DEPTH)/starboard/shared/iso/double_exponent.cc', - '<(DEPTH)/starboard/shared/iso/double_floor.cc', - '<(DEPTH)/starboard/shared/iso/double_is_finite.cc', - '<(DEPTH)/starboard/shared/iso/double_is_nan.cc', - '<(DEPTH)/starboard/shared/iso/memory_compare.cc', - '<(DEPTH)/starboard/shared/iso/memory_copy.cc', - '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc', - '<(DEPTH)/starboard/shared/iso/memory_move.cc', - '<(DEPTH)/starboard/shared/iso/memory_set.cc', - '<(DEPTH)/starboard/shared/iso/string_compare.cc', - '<(DEPTH)/starboard/shared/iso/string_compare_all.cc', - '<(DEPTH)/starboard/shared/iso/string_find_character.cc', - '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc', - '<(DEPTH)/starboard/shared/iso/string_find_string.cc', - '<(DEPTH)/starboard/shared/iso/string_get_length.cc', - '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc', - '<(DEPTH)/starboard/shared/iso/string_parse_double.cc', - '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc', - '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc', - '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc', - '<(DEPTH)/starboard/shared/iso/string_scan.cc', - '<(DEPTH)/starboard/shared/iso/system_binary_search.cc', - '<(DEPTH)/starboard/shared/iso/system_sort.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc', - '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc', - '<(DEPTH)/starboard/shared/linux/byte_swap.cc', - '<(DEPTH)/starboard/shared/linux/get_home_directory.cc', - '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc', - '<(DEPTH)/starboard/shared/linux/page_internal.cc', - '<(DEPTH)/starboard/shared/linux/socket_get_local_interface_address.cc', - '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc', - '<(DEPTH)/starboard/shared/linux/system_get_stack.cc', - '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc', - '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc', - '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc', - '<(DEPTH)/starboard/shared/linux/system_symbolize.cc', - '<(DEPTH)/starboard/shared/linux/thread_get_id.cc', - '<(DEPTH)/starboard/shared/linux/thread_get_name.cc', - '<(DEPTH)/starboard/shared/linux/thread_set_name.cc', - '<(DEPTH)/starboard/shared/nouser/user_get_current.cc', - '<(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/posix/directory_create.cc', - '<(DEPTH)/starboard/shared/posix/file_can_open.cc', - '<(DEPTH)/starboard/shared/posix/file_close.cc', - '<(DEPTH)/starboard/shared/posix/file_delete.cc', - '<(DEPTH)/starboard/shared/posix/file_exists.cc', - '<(DEPTH)/starboard/shared/posix/file_flush.cc', - '<(DEPTH)/starboard/shared/posix/file_get_info.cc', - '<(DEPTH)/starboard/shared/posix/file_get_path_info.cc', - '<(DEPTH)/starboard/shared/posix/file_open.cc', - '<(DEPTH)/starboard/shared/posix/file_read.cc', - '<(DEPTH)/starboard/shared/posix/file_seek.cc', - '<(DEPTH)/starboard/shared/posix/file_truncate.cc', - '<(DEPTH)/starboard/shared/posix/file_write.cc', - '<(DEPTH)/starboard/shared/posix/log.cc', - '<(DEPTH)/starboard/shared/posix/log_flush.cc', - '<(DEPTH)/starboard/shared/posix/log_format.cc', - '<(DEPTH)/starboard/shared/posix/log_is_tty.cc', - '<(DEPTH)/starboard/shared/posix/log_raw.cc', - '<(DEPTH)/starboard/shared/posix/memory_flush.cc', - '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc', - '<(DEPTH)/starboard/shared/posix/socket_accept.cc', - '<(DEPTH)/starboard/shared/posix/socket_bind.cc', - '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc', - '<(DEPTH)/starboard/shared/posix/socket_connect.cc', - '<(DEPTH)/starboard/shared/posix/socket_create.cc', - '<(DEPTH)/starboard/shared/posix/socket_destroy.cc', - '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc', - '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc', - '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc', - '<(DEPTH)/starboard/shared/posix/socket_internal.cc', - '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc', - '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc', - '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc', - '<(DEPTH)/starboard/shared/posix/socket_listen.cc', - '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc', - '<(DEPTH)/starboard/shared/posix/socket_resolve.cc', - '<(DEPTH)/starboard/shared/posix/socket_send_to.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc', - '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc', - '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc', - '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc', - '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc', - '<(DEPTH)/starboard/shared/posix/string_format.cc', - '<(DEPTH)/starboard/shared/posix/string_format_wide.cc', - '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc', - '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc', - '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc', - '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc', - '<(DEPTH)/starboard/shared/posix/system_get_locale_id.cc', - '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc', - '<(DEPTH)/starboard/shared/posix/thread_sleep.cc', - '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc', - '<(DEPTH)/starboard/shared/posix/time_get_now.cc', - '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc', - '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc', - '<(DEPTH)/starboard/shared/posix/time_zone_get_name.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc', - '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_create.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc', - '<(DEPTH)/starboard/shared/pthread/mutex_release.cc', - '<(DEPTH)/starboard/shared/pthread/once.cc', - '<(DEPTH)/starboard/shared/pthread/thread_create.cc', - '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc', - '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc', - '<(DEPTH)/starboard/shared/pthread/thread_detach.cc', - '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc', - '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc', - '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc', - '<(DEPTH)/starboard/shared/pthread/thread_join.cc', - '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc', - '<(DEPTH)/starboard/shared/pthread/thread_yield.cc', - '<(DEPTH)/starboard/shared/signal/crash_signals.h', - '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc', - '<(DEPTH)/starboard/shared/signal/suspend_signals.cc', - '<(DEPTH)/starboard/shared/signal/suspend_signals.h', - '<(DEPTH)/starboard/shared/starboard/application.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h', - '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_is_valid.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc', - '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.h', - '<(DEPTH)/starboard/shared/starboard/directory_can_open.cc', - '<(DEPTH)/starboard/shared/starboard/event_cancel.cc', - '<(DEPTH)/starboard/shared/starboard/event_schedule.cc', - '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc', - '<(DEPTH)/starboard/shared/starboard/file_storage/storage_write_record.cc', - '<(DEPTH)/starboard/shared/starboard/log_message.cc', - '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc', - '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc', - '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc', - '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc', - '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc', - '<(DEPTH)/starboard/shared/starboard/media/mime_parser.cc', - '<(DEPTH)/starboard/shared/starboard/media/mime_parser.h', - '<(DEPTH)/starboard/shared/starboard/new.cc', - '<(DEPTH)/starboard/shared/starboard/player/audio_decoder_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/player_create.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_destroy.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_set_bounds.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_set_pause.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_worker.h', - '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc', - '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc', - '<(DEPTH)/starboard/shared/starboard/player/video_decoder_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.h', - '<(DEPTH)/starboard/shared/starboard/queue_application.cc', - '<(DEPTH)/starboard/shared/starboard/string_concat.cc', - '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc', - '<(DEPTH)/starboard/shared/starboard/string_copy.cc', - '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc', - '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc', - '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc', - '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc', - '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc', - '<(DEPTH)/starboard/shared/stub/drm_close_session.cc', - '<(DEPTH)/starboard/shared/stub/drm_create_system.cc', - '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc', - '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc', - '<(DEPTH)/starboard/shared/stub/drm_system_internal.h', - '<(DEPTH)/starboard/shared/stub/drm_update_session.cc', - '<(DEPTH)/starboard/shared/stub/media_is_supported.cc', - '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc', - '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc', - '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc', - '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc', - '<(DEPTH)/starboard/shared/stub/system_raise_platform_error.cc', - '<(DEPTH)/starboard/shared/x11/application_x11.cc', - '<(DEPTH)/starboard/shared/x11/window_create.cc', - '<(DEPTH)/starboard/shared/x11/window_destroy.cc', - '<(DEPTH)/starboard/shared/x11/window_get_platform_handle.cc', - '<(DEPTH)/starboard/shared/x11/window_get_size.cc', - '<(DEPTH)/starboard/shared/x11/window_internal.cc', - ], + 'sources': ['<@(starboard_platform_sources)'], 'defines': [ # This must be defined when building Starboard, and must not when # building Starboard client code. 'STARBOARD_IMPLEMENTATION', ], 'dependencies': [ - '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc', - '<(DEPTH)/third_party/libevent/libevent.gyp:libevent', - 'starboard_base_symbolize', + '<@(starboard_platform_dependencies)', ], }, ],
diff --git a/src/starboard/linux/x64x11/starboard_platform.gypi b/src/starboard/linux/x64x11/starboard_platform.gypi new file mode 100644 index 0000000..05f1e12 --- /dev/null +++ b/src/starboard/linux/x64x11/starboard_platform.gypi
@@ -0,0 +1,30 @@ +# Copyright 2016 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. +{ + 'includes': ['../shared/starboard_platform.gypi'], + + 'variables': { + 'starboard_platform_sources': [ + '<(DEPTH)/starboard/linux/x64x11/main.cc', + '<(DEPTH)/starboard/linux/x64x11/sanitizer_options.cc', + '<(DEPTH)/starboard/linux/x64x11/system_get_property.cc', + '<(DEPTH)/starboard/shared/x11/application_x11.cc', + '<(DEPTH)/starboard/shared/x11/window_create.cc', + '<(DEPTH)/starboard/shared/x11/window_destroy.cc', + '<(DEPTH)/starboard/shared/x11/window_get_platform_handle.cc', + '<(DEPTH)/starboard/shared/x11/window_get_size.cc', + '<(DEPTH)/starboard/shared/x11/window_internal.cc', + ], + }, +}
diff --git a/src/starboard/linux/x64x11/starboard_platform_tests.gyp b/src/starboard/linux/x64x11/starboard_platform_tests.gyp new file mode 100644 index 0000000..0250125 --- /dev/null +++ b/src/starboard/linux/x64x11/starboard_platform_tests.gyp
@@ -0,0 +1,41 @@ +# Copyright 2016 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. +{ + 'targets': [ + { + 'target_name': 'starboard_platform_tests', + 'type': '<(gtest_target_type)', + 'sources': [ + '<(DEPTH)/starboard/common/test_main.cc', + '<(DEPTH)/starboard/shared/starboard/media/mime_type_test.cc', + ], + 'dependencies': [ + '<(DEPTH)/starboard/starboard.gyp:starboard', + '<(DEPTH)/testing/gmock.gyp:gmock', + '<(DEPTH)/testing/gtest.gyp:gtest', + ], + }, + { + 'target_name': 'starboard_platform_tests_deploy', + 'type': 'none', + 'dependencies': [ + '<(DEPTH)/<(starboard_path)/starboard_platform_tests.gyp:starboard_platform_tests', + ], + 'variables': { + 'executable_name': 'starboard_platform_tests', + }, + 'includes': [ '../../build/deploy.gypi' ], + }, + ], +}
diff --git a/src/starboard/linux/x64x11/system_get_property.cc b/src/starboard/linux/x64x11/system_get_property.cc index cffaa35..b72747d 100644 --- a/src/starboard/linux/x64x11/system_get_property.cc +++ b/src/starboard/linux/x64x11/system_get_property.cc
@@ -47,6 +47,7 @@ case kSbSystemPropertyModelName: case kSbSystemPropertyModelYear: case kSbSystemPropertyNetworkOperatorName: + case kSbSystemPropertySpeechApiKey: return false; case kSbSystemPropertyFriendlyName:
diff --git a/src/starboard/log.h b/src/starboard/log.h index 3693925..8e86cd8 100644 --- a/src/starboard/log.h +++ b/src/starboard/log.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Debug logging. +// Module Overview: Starboard Logging module +// +// Defines debug logging functions. #ifndef STARBOARD_LOG_H_ #define STARBOARD_LOG_H_ @@ -41,28 +43,38 @@ kSbLogPriorityFatal, } SbLogPriority; -// Writes |message| at |priority| to the debug output log for this platform. -// Passing kSbLogPriorityFatal will not terminate the program, such policy must -// be enforced at the application level. |priority| may, in fact, be completely -// ignored on many platforms. No formatting is required to be done on the -// message, not even newline termination. That said, platforms may wish to -// adjust the message to be more suitable for their output method. This could be -// wrapping the text, or stripping unprintable characters. +// Writes |message| to the platform's debug output log. +// +// |priority|: The SbLogPriority at which the message should be logged. Note +// that passing |kSbLogPriorityFatal| does not terminate the program. Such a +// policy must be enforced at the application level. In fact, |priority| may +// be completely ignored on many platforms. +// |message|: The message to be logged. No formatting is required to be done +// on the value, including newline termination. That said, platforms can +// adjust the message to be more suitable for their output method by +// wrapping the text, stripping unprintable characters, etc. SB_EXPORT void SbLog(SbLogPriority priority, const char* message); // A bare-bones log output method that is async-signal-safe, i.e. safe to call -// from an asynchronous signal handler (e.g. a SIGSEGV handler). It should do no -// additional formatting. +// from an asynchronous signal handler (e.g. a |SIGSEGV| handler). It should not +// do any additional formatting. +// +// |message|: The message to be logged. SB_EXPORT void SbLogRaw(const char* message); // Dumps the stack of the current thread to the log in an async-signal-safe -// manner, i.e. safe to call from an asynchronous signal handler (e.g. a SIGSEGV -// handler). Does not include SbLogRawDumpStack itself, and will additionally -// skip |frames_to_skip| frames from the top of the stack. +// manner, i.e. safe to call from an asynchronous signal handler (e.g. a +// |SIGSEGV| handler). Does not include SbLogRawDumpStack itself. +// +// |frames_to_skip|: The number of frames to skip from the top of the stack +// when dumping the current thread to the log. This parameter lets you remove +// noise from helper functions that might end up on top of every stack dump +// so that the stack dump is just the relevant function stack where the +// problem occurred. SB_EXPORT void SbLogRawDumpStack(int frames_to_skip); // A formatted log output method that is async-signal-safe, i.e. safe to call -// from an asynchronous signal handler (e.g. a SIGSEGV handler). +// from an asynchronous signal handler (e.g. a |SIGSEGV| handler). SB_EXPORT void SbLogRawFormat(const char* format, va_list args) SB_PRINTF_FORMAT(1, 0); @@ -76,12 +88,12 @@ va_end(args); } -// A log output method, that additionally performs a string format on the way -// out. +// A log output method that additionally performs a string format on the +// data being logged. SB_EXPORT void SbLogFormat(const char* format, va_list args) SB_PRINTF_FORMAT(1, 0); -// Inline wrapper of SbLogFormat to convert from ellipsis to va_args. +// Inline wrapper of SbLogFormat that converts from ellipsis to va_args. static SB_C_INLINE void SbLogFormatF(const char* format, ...) SB_PRINTF_FORMAT(1, 2); void SbLogFormatF(const char* format, ...) { @@ -91,10 +103,10 @@ va_end(args); } -// Flushes the log buffer, on some platforms. +// Flushes the log buffer on some platforms. SB_EXPORT void SbLogFlush(); -// Returns whether the log output goes to a TTY or is being redirected. +// Indicates whether the log output goes to a TTY or is being redirected. SB_EXPORT bool SbLogIsTty(); #ifdef __cplusplus
diff --git a/src/starboard/media.h b/src/starboard/media.h index 89f2608..2c800ba 100644 --- a/src/starboard/media.h +++ b/src/starboard/media.h
@@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Media definitions that are common between the Decoder and Player interfaces. +// Module Overview: Starboard Media module +// +// Provides media definitions that are common between the Decoder and Player +// interfaces. #ifndef STARBOARD_MEDIA_H_ #define STARBOARD_MEDIA_H_ @@ -40,7 +43,7 @@ kSbMediaTypeVideo, } SbMediaType; -// Possibly supported types of video elementary streams. +// Types of video elementary streams that could be supported. typedef enum SbMediaVideoCodec { kSbMediaVideoCodecNone, @@ -54,7 +57,7 @@ kSbMediaVideoCodecVp9, } SbMediaVideoCodec; -// Possibly supported types of audio elementary streams. +// Types of audio elementary streams that can be supported. typedef enum SbMediaAudioCodec { kSbMediaAudioCodecNone, @@ -63,8 +66,9 @@ kSbMediaAudioCodecVorbis, } SbMediaAudioCodec; -// Types of media support which is a direct map as the result of canPlayType() -// specified in the following link: +// Indicates how confident the device is that it can play media resources +// of the given type. The values are a direct map of the canPlayType() method +// specified at the following link: // https://www.w3.org/TR/2011/WD-html5-20110113/video.html#dom-navigator-canplaytype typedef enum SbMediaSupportType { // The media type cannot be played. @@ -111,24 +115,27 @@ kSbMediaAudioSampleTypeFloat32, } SbMediaAudioSampleType; -// Possible audio frame storage types. Interleaved means the samples of a -// multi-channel audio stream are stored in one continuous buffer, samples at -// the same timestamp are stored one after another. Planar means the samples -// of each channel are stored in their own continuous buffer. For example, for -// stereo stream with channels L and R that contains samples with timestamp -// 0, 1, ..., interleaved means the samples are stored in one buffer as -// "L0 R0 L1 R1 L2 R2 ...". Planar means the samples are stored in two buffers -// "L0 L1 L2 ..." and "R0 R1 R2 ...". +// Possible audio frame storage types. typedef enum SbMediaAudioFrameStorageType { + // The samples of a multi-channel audio stream are stored in one continuous + // buffer. Samples at the same timestamp are stored one after another. For + // example, for a stereo stream with channels L and R that contains samples + // with timestamps 0, 1, 2, etc., the samples are stored in one buffer as + // "L0 R0 L1 R1 L2 R2 ...". kSbMediaAudioFrameStorageTypeInterleaved, + + // The samples of each channel are stored in their own continuous buffer. + // For example, for a stereo stream with channels L and R that contains + // samples with timestamps 0, 1, 2, etc., the samples are stored in two + // buffers "L0 L1 L2 ..." and "R0 R1 R2 ...". kSbMediaAudioFrameStorageTypePlanar, } SbMediaAudioFrameStorageType; // The set of information required by the decoder or player for each video // sample. typedef struct SbMediaVideoSampleInfo { - // Whether the associated sample is a key frame (I-frame). Video key frames - // must always start with SPS and PPS NAL units. + // Indicates whether the associated sample is a key frame (I-frame). + // Video key frames must always start with SPS and PPS NAL units. bool is_key_frame; // The frame width of this sample, in pixels. Also could be parsed from the @@ -148,11 +155,11 @@ // The platform-defined index of the associated audio output. int index; - // The type of audio connector. Will be the empty kSbMediaAudioConnectorNone + // The type of audio connector. Will be the empty |kSbMediaAudioConnectorNone| // if this device cannot provide this information. SbMediaAudioConnector connector; - // The expected latency of audio over this output, in microseconds, or 0 if + // The expected latency of audio over this output, in microseconds, or |0| if // this device cannot provide this information. SbTime latency; @@ -160,8 +167,8 @@ SbMediaAudioCodingType coding_type; // The number of audio channels currently supported by this device output, or - // 0 if this device cannot provide this information, in which case the caller - // can probably assume stereo output. + // |0| if this device cannot provide this information, in which case the + // caller can probably assume stereo output. int number_of_channels; } SbMediaAudioConfiguration; @@ -170,18 +177,14 @@ // decoder. // // The Sequence Header consists of a little-endian hexadecimal encoded -// WAVEFORMATEX structure followed by an Audio-specific configuration field. -// -// Specification of WAVEFORMATEX structure: +// |WAVEFORMATEX| structure followed by an Audio-specific configuration field. +// The |WAVEFORMATEX| structure is specified at: // http://msdn.microsoft.com/en-us/library/dd390970(v=vs.85).aspx -// -// AudioSpecificConfig defined here in section 1.6.2.1: -// http://read.pudn.com/downloads98/doc/comm/401153/14496/ISO_IEC_14496-3%20Part%203%20Audio/C036083E_SUB1.PDF typedef struct SbMediaAudioHeader { // The waveform-audio format type code. uint16_t format_tag; - // The number of audio channels in this format. 1 for mono, 2 for stereo. + // The number of audio channels in this format. |1| for mono, |2| for stereo. uint16_t number_of_channels; // The sampling rate. @@ -193,13 +196,14 @@ // Byte block alignment, e.g, 4. uint16_t block_alignment; - // The bit depth for the stream this represents, e.g. 8 or 16. + // The bit depth for the stream this represents, e.g. |8| or |16|. uint16_t bits_per_sample; // The size, in bytes, of the audio_specific_config. uint16_t audio_specific_config_size; - // The AudioSpecificConfig, as specified in ISO/IEC-14496-3. + // The AudioSpecificConfig, as specified in ISO/IEC-14496-3, section 1.6.2.1: + // http://read.pudn.com/downloads98/doc/comm/401153/14496/ISO_IEC_14496-3%20Part%203%20Audio/C036083E_SUB1.PDF int8_t audio_specific_config[8]; } SbMediaAudioHeader; @@ -209,73 +213,99 @@ // --- Functions ------------------------------------------------------------- -// Returns whether decoding |video_codec|, |audio_codec|, and decrypting using -// |key_system| is supported together by this platform. If |video_codec| is -// kSbMediaVideoCodecNone or |audio_codec| is kSbMediaAudioCodecNone, this -// function should return true if |key_system| is supported on the platform to -// decode any supported input formats. +// Indicates whether this platform supports decoding |video_codec| and +// |audio_codec| along with decrypting using |key_system|. If |video_codec| is +// |kSbMediaVideoCodecNone| or if |audio_codec| is |kSbMediaAudioCodecNone|, +// this function should return |true| as long as |key_system| is supported on +// the platform to decode any supported input formats. +// +// |video_codec|: The |SbMediaVideoCodec| being checked for platform +// compatibility. +// |audio_codec|: The |SbMediaAudioCodec| being checked for platform +// compatibility. +// |key_system|: The key system being checked for platform compatibility. SB_EXPORT bool SbMediaIsSupported(SbMediaVideoCodec video_codec, SbMediaAudioCodec audio_codec, const char* key_system); -// Returns whether a given combination of |frame_width| x |frame_height| frames -// at |bitrate| and |fps| is supported on this platform with |video_codec|. If -// |video_codec| is not supported under any condition, this function will always -// return false. +// Indicates whether a given combination of +// (|frame_width| x |frame_height|) frames at |bitrate| and |fps| is supported +// on this platform with |video_codec|. If |video_codec| is not supported under +// any condition, this function returns |false|. // -// Any of the parameters may be set to 0 to mean that they shouldn't be +// Setting any of the parameters to |0| indicates that they shouldn't be // considered. +// +// |video_codec|: The video codec used in the media content. +// |frame_width|: The frame width of the media content. +// |frame_height|: The frame height of the media content. +// |bitrate|: The bitrate of the media content. +// |fps|: The number of frames per second in the media content. SB_EXPORT bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec, int frame_width, int frame_height, int64_t bitrate, int fps); -// Returns whether |audio_codec| is supported on this platform at |bitrate|. If -// |audio_codec| is not supported under any condition, this function will always -// return false. +// Indicates whether this platform supports |audio_codec| at |bitrate|. +// If |audio_codec| is not supported under any condition, this function +// returns |false|. +// +// |audio_codec|: The media's audio codec (|SbMediaAudioCodec|). +// |bitrate|: The media's bitrate. SB_EXPORT bool SbMediaIsAudioSupported(SbMediaVideoCodec audio_codec, int64_t bitrate); -// Returns information on whether the playback of the specific media described -// by |mime| and encrypted using |key_system| can be played. -// |mime| contains the mime information of the media in the form of 'video/webm' -// or 'video/mp4; codecs="avc1.42001E"'. It may include arbitrary parameters -// like "codecs", "channels", etc.. -// |key_system| should be a lower case in fhe form of -// "com.example.somesystem" as suggested by -// https://w3c.github.io/encrypted-media/#key-system +// Returns information about whether the playback of the specific media +// described by |mime| and encrypted using |key_system| can be played. +// +// Note that neither |mime| nor |key_system| can be NULL. This function returns +// |kSbMediaSupportNotSupported| if either is NULL. +// +// |mime|: The mime information of the media in the form of |video/webm| +// or |video/mp4; codecs="avc1.42001E"|. It may include arbitrary parameters +// like "codecs", "channels", etc. +// |key_system|: A lowercase value in fhe form of "com.example.somesystem" +// as suggested by https://w3c.github.io/encrypted-media/#key-system // that can be matched exactly with known DRM key systems of the platform. // When |key_system| is an empty string, the return value is an indication for // non-encrypted media. -// Note that both |mime| and |key_system| cannot be NULL. This function returns -// kSbMediaSupportNotSupported if any of them is NULL. SB_EXPORT SbMediaSupportType SbMediaCanPlayMimeAndKeySystem(const char* mime, const char* key_system); -// Returns the number of audio outputs currently available on this device. It -// is expected that, even if the number of outputs or their audio configurations -// can't be determined, the platform will at least return a single output that -// supports at least stereo. +// Returns the number of audio outputs currently available on this device. +// Even if the number of outputs or their audio configurations can't be +// determined, it is expected that the platform will at least return a single +// output that supports at least stereo. SB_EXPORT int SbMediaGetAudioOutputCount(); -// Places the current physical audio configuration of audio output -// |output_index| on this device into |out_configuration|, which must not be -// NULL, or returns false if nothing could be determined on this platform, or if -// |output_index| doesn't exist on this device. +// Retrieves the current physical audio configuration of audio output +// |output_index| on this device and places it in |out_configuration|, +// which must not be NULL. +// +// This function returns |false| if nothing could be determined on this +// platform or if |output_index| does not exist on this device. +// +// |out_configuration|: The variable that holds the audio configuration +// information. SB_EXPORT bool SbMediaGetAudioConfiguration( int output_index, SbMediaAudioConfiguration* out_configuration); -// Returns whether output copy protection is currently enabled on all capable -// outputs. If true, then non-protection-capable outputs are expected to be +// Indicates whether output copy protection is currently enabled on all capable +// outputs. If |true|, then non-protection-capable outputs are expected to be // blanked. SB_EXPORT bool SbMediaIsOutputProtected(); // Enables or disables output copy protection on all capable outputs. If // enabled, then non-protection-capable outputs are expected to be blanked. -// Returns whether the operation was successful. Returns a success even if the -// call was redundant. +// +// The return value indicates whether the operation was successful, and the +// function returns a success even if the call is redundant in that it doesn't +// change the current value. +// +// |enabled|: Indicates whether output protection is enabled (|true|) or +// disabled. SB_EXPORT bool SbMediaSetOutputProtection(bool enabled); #ifdef __cplusplus
diff --git a/src/starboard/memory.h b/src/starboard/memory.h index f418c30..51102d6 100644 --- a/src/starboard/memory.h +++ b/src/starboard/memory.h
@@ -12,11 +12,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Memory allocation, alignment, copying, and comparing. +// Module Overview: Starboard Memory module +// +// Defines functions for memory allocation, alignment, copying, and comparing. +// +// #### Porters +// +// All of the "Unchecked" and "Free" functions must be implemented, but they +// should not be called directly. The Starboard platform wraps them with extra +// accounting under certain circumstances. +// +// #### Porters and Cobalt developers +// +// Nobody should call the "Checked", "Unchecked" or "Free" functions directly +// because that evades Starboard's memory tracking. In both port implementations +// and Cobalt code, you should always call SbMemoryAllocate and +// SbMemoryDeallocate rather than SbMemoryAllocateUnchecked and SbMemoryFree. +// +// - The "checked" functions are SbMemoryAllocateChecked(), +// SbMemoryReallocateChecked(), and SbMemoryAllocateAlignedChecked(). +// - The "unchecked" functions are SbMemoryAllocateUnchecked(), +// SbMemoryReallocateUnchecked(), and SbMemoryAllocateAlignedUnchecked(). +// - The "free" functions are SbMemoryFree() and SbMemoryFreeAligned(). #ifndef STARBOARD_MEMORY_H_ #define STARBOARD_MEMORY_H_ +#include "starboard/configuration.h" #include "starboard/export.h" #include "starboard/system.h" #include "starboard/types.h" @@ -27,16 +49,6 @@ #define SB_MEMORY_MAP_FAILED ((void*)-1) // NOLINT(readability/casting) -#if defined(SB_ABORT_ON_ALLOCATION_FAILURE) -#define SbMemoryAllocate SbMemoryAllocateChecked -#define SbMemoryReallocate SbMemoryReallocateChecked -#define SbMemoryAllocateAligned SbMemoryAllocateAlignedChecked -#else -#define SbMemoryAllocate SbMemoryAllocateUnchecked -#define SbMemoryReallocate SbMemoryReallocateUnchecked -#define SbMemoryAllocateAligned SbMemoryAllocateAlignedUnchecked -#endif - // The bitwise OR of these flags should be passed to SbMemoryMap to indicate // how the mapped memory can be used. typedef enum SbMemoryMapFlags { @@ -68,79 +80,125 @@ } } -// Allocates a chunk of memory of at least |size| bytes, returning it. If unable -// to allocate the memory, it returns NULL. If |size| is 0, it may return NULL -// or it may return a unique pointer value that can be passed to -// SbMemoryFree. Meant to be a drop-in replacement for malloc. -SB_EXPORT void* SbMemoryAllocateUnchecked(size_t size); +// Allocates and returns a chunk of memory of at least |size| bytes. This +// function should be called from the Cobalt codebase. It is intended to +// be a drop-in replacement for |malloc|. +// +// Note that this function returns |NULL| if it is unable to allocate the +// memory. +// +// |size|: The amount of memory to be allocated. If |size| is 0, the function +// may return |NULL| or it may return a unique pointer value that can be +// passed to SbMemoryDeallocate. +SB_EXPORT void* SbMemoryAllocate(size_t size); -// Same as SbMemoryAllocateUnchecked, but will abort() in the case of an -// allocation failure. -static SB_C_FORCE_INLINE void* SbMemoryAllocateChecked(size_t size) { - void* address = SbMemoryAllocateUnchecked(size); - SbAbortIfAllocationFailed(size, address); - return address; -} +// Attempts to resize |memory| to be at least |size| bytes, without touching +// the contents of memory. +// - If the function cannot perform the fast resize, it allocates a new chunk +// of memory, copies the contents over, and frees the previous chunk, +// returning a pointer to the new chunk. +// - If the function cannot perform the slow resize, it returns |NULL|, +// leaving the given memory chunk unchanged. +// +// This function should be called from the Cobalt codebase. It is meant +// to be a drop-in replacement for |realloc|. +// +// |memory|: The chunk of memory to be resized. |memory| may be NULL, in which +// case it behaves exactly like SbMemoryAllocateUnchecked. +// |size|: The size to which |memory| will be resized. If |size| is |0|, +// the function may return |NULL| or it may return a unique pointer value +// that can be passed to SbMemoryDeallocate. +SB_EXPORT void* SbMemoryReallocate(void* memory, size_t size); -// Attempts to resize |memory| to be at least |size| bytes, without touching the -// contents. If it cannot perform the fast resize, it will allocate a new chunk -// of memory, copy the contents over, and free the previous chunk, returning a -// pointer to the new chunk. If it cannot perform the slow resize, it will -// return NULL, leaving the given memory chunk unchanged. |memory| may be NULL, -// in which case it behaves exactly like SbMemoryAllocateUnchecked. If |size| -// is 0, it may return NULL or it may return a unique pointer value that can be -// passed to SbMemoryFree. Meant to be a drop-in replacement for realloc. -SB_EXPORT void* SbMemoryReallocateUnchecked(void* memory, size_t size); +// Allocates and returns a chunk of memory of at least |size| bytes, aligned +// to |alignment|. This function should be called from the Cobalt codebase. +// It is meant to be a drop-in replacement for |memalign|. +// +// The function returns |NULL| if it cannot allocate the memory. In addition, +// the function's behavior is undefined if |alignment| is not a power of two. +// +// |alignment|: The way that data is arranged and accessed in memory. The value +// must be a power of two. +// |size|: The size of the memory to be allocated. If |size| is |0|, the +// function may return |NULL| or it may return a unique aligned pointer value +// that can be passed to SbMemoryDeallocateAligned. +SB_EXPORT void* SbMemoryAllocateAligned(size_t alignment, size_t size); -// Same as SbMemoryReallocateUnchecked, but will abort() in the case of an -// allocation failure -static SB_C_FORCE_INLINE void* SbMemoryReallocateChecked(void* memory, - size_t size) { - void* address = SbMemoryReallocateUnchecked(memory, size); - SbAbortIfAllocationFailed(size, address); - return address; -} +// Frees a previously allocated chunk of memory. If |memory| is NULL, then the +// operation is a no-op. This function should be called from the Cobalt +// codebase. It is meant to be a drop-in replacement for |free|. +// +// |memory|: The chunk of memory to be freed. +SB_EXPORT void SbMemoryDeallocate(void* memory); -// Frees a previously allocated chunk of memory. If |memory| is NULL, then it -// does nothing. Meant to be a drop-in replacement for free. -SB_EXPORT void SbMemoryFree(void* memory); +// Frees a previously allocated chunk of aligned memory. This function should +// be called from the Cobalt codebase. It is meant to be a drop-in replacement +// for |_aligned_free|. -// Allocates a chunk of memory of at least |size| bytes, aligned to |alignment|, -// returning it. |alignment| must be a power of two, otherwise behavior is -// undefined. If unable to allocate the memory, it returns NULL. If |size| is 0, -// it may return NULL or it may return a unique aligned pointer value that can -// be passed to SbMemoryFreeAligned. Meant to be a drop-in replacement for -// memalign. -SB_EXPORT void* SbMemoryAllocateAlignedUnchecked(size_t alignment, size_t size); +// |memory|: The chunk of memory to be freed. If |memory| is NULL, then the +// function is a no-op. +SB_EXPORT void SbMemoryDeallocateAligned(void* memory); -// Same as SbMemoryAllocateAlignedUnchecked, but will abort() in the case of an -// allocation failure -static SB_C_FORCE_INLINE void* SbMemoryAllocateAlignedChecked(size_t alignment, - size_t size) { - void* address = SbMemoryAllocateAlignedUnchecked(alignment, size); - SbAbortIfAllocationFailed(size, address); - return address; -} +///////////////////////////////////////////////////////////////// +// The following functions must be provided by Starboard ports. +///////////////////////////////////////////////////////////////// -// Frees a previously allocated chunk of aligned memory. If |memory| is NULL, -// then it does nothing. Meant to be a drop-in replacement for _aligned_free. -SB_EXPORT void SbMemoryFreeAligned(void* memory); +// This is the implementation of SbMemoryAllocate that must be +// provided by Starboard ports. +// +// DO NOT CALL. Call SbMemoryAllocate(...) instead. +SB_DEPRECATED_EXTERNAL( + SB_EXPORT void* SbMemoryAllocateUnchecked(size_t size)); + +// This is the implementation of SbMemoryReallocate that must be +// provided by Starboard ports. +// +// DO NOT CALL. Call SbMemoryReallocate(...) instead. +SB_DEPRECATED_EXTERNAL( + SB_EXPORT void* SbMemoryReallocateUnchecked(void* memory, size_t size)); + +// This is the implementation of SbMemoryAllocateAligned that must be +// provided by Starboard ports. +// +// DO NOT CALL. Call SbMemoryAllocateAligned(...) instead. +SB_DEPRECATED_EXTERNAL( + SB_EXPORT void* SbMemoryAllocateAlignedUnchecked(size_t alignment, + size_t size)); + +// This is the implementation of SbMemoryDeallocate that must be provided by +// Starboard ports. +// +// DO NOT CALL. Call SbMemoryDeallocate(...) instead. +SB_DEPRECATED_EXTERNAL( + SB_EXPORT void SbMemoryFree(void* memory)); + +// This is the implementation of SbMemoryFreeAligned that must be provided by +// Starboard ports. +// +// DO NOT CALL. Call SbMemoryDeallocateAligned(...) instead. +SB_DEPRECATED_EXTERNAL( + SB_EXPORT void SbMemoryFreeAligned(void* memory)); #if SB_HAS(MMAP) // Allocates |size_bytes| worth of physical memory pages and maps them into an -// available virtual region. |flags| is the bitwise or of the protection flags -// for the mapped memory as specified in SbMemoryMapFlags. On some platforms, -// |name| appears in the debugger and can be up to 32 bytes. Returns -// SB_MEMORY_MAP_FAILED on failure, as NULL is a valid return value. +// available virtual region. This function returns |SB_MEMORY_MAP_FAILED| on +// failure. |NULL| is a valid return value. +// +// |size_bytes|: The amount of physical memory pages to be allocated. +// |flags|: The bitwise OR of the protection flags for the mapped memory +// as specified in |SbMemoryMapFlags|. +// |name|: A value that appears in the debugger on some platforms. The value +// can be up to 32 bytes. SB_EXPORT void* SbMemoryMap(int64_t size_bytes, int flags, const char* name); // Unmap |size_bytes| of physical pages starting from |virtual_address|, -// returning true on success. After this, [virtual_address, virtual_address + -// size_bytes) will not be read/writable. SbMemoryUnmap() can unmap multiple -// contiguous regions that were mapped with separate calls to -// SbMemoryMap(). E.g. if one call to SbMemoryMap(0x1000) returns (void*)0xA000 -// and another call to SbMemoryMap(0x1000) returns (void*)0xB000, -// SbMemoryUnmap(0xA000, 0x2000) should free both. +// returning |true| on success. After this function completes, +// [virtual_address, virtual_address + size_bytes) will not be read/writable. +// This function can unmap multiple contiguous regions that were mapped with +// separate calls to SbMemoryMap(). For example, if one call to +// |SbMemoryMap(0x1000)| returns |(void*)0xA000|, and another call to +// |SbMemoryMap(0x1000)| returns |(void*)0xB000|, +// |SbMemoryUnmap(0xA000, 0x2000)| should free both regions. SB_EXPORT bool SbMemoryUnmap(void* virtual_address, int64_t size_bytes); #if SB_CAN(MAP_EXECUTABLE_MEMORY) @@ -152,51 +210,78 @@ #endif #endif // SB_HAS(MMAP) -// Gets the stack bounds for the current thread, placing the highest addressable -// byte + 1 in |out_high|, and the lowest addressable byte in |out_low|. +// Gets the stack bounds for the current thread. +// +// |out_high|: The highest addressable byte + 1 for the current thread. +// |out_low|: The lowest addressable byte for the current thread. SB_EXPORT void SbMemoryGetStackBounds(void** out_high, void** out_low); // Copies |count| sequential bytes from |source| to |destination|, without -// support for the |source| and |destination| regions overlapping. Behavior is -// undefined if |destination| or |source| are NULL. Does nothing if |count| is -// 0. Returns |destination|, for some reason. Meant to be a drop-in replacement -// for memcpy. +// support for the |source| and |destination| regions overlapping. This +// function is meant to be a drop-in replacement for |memcpy|. +// +// The function's behavior is undefined if |destination| or |source| are NULL, +// and the function is a no-op if |count| is 0. The return value is +// |destination|. +// +// |destination|: The destination of the copied memory. +// |source|: The source of the copied memory. +// |count|: The number of sequential bytes to be copied. SB_EXPORT void* SbMemoryCopy(void* destination, const void* source, size_t count); // Copies |count| sequential bytes from |source| to |destination|, with support -// for the |source| and |destination| regions overlapping. Behavior is -// undefined if |destination| or |source| are NULL. Does nothing if |count| is -// 0. Returns |destination|, for some reason. Meant to be a drop-in replacement -// for memmove. +// for the |source| and |destination| regions overlapping. This function is +// meant to be a drop-in replacement for |memmove|. +// +// The function's behavior is undefined if |destination| or |source| are NULL, +// and the function is a no-op if |count| is 0. The return value is +// |destination|. +// +// |destination|: The destination of the copied memory. +// |source|: The source of the copied memory. +// |count|: The number of sequential bytes to be copied. SB_EXPORT void* SbMemoryMove(void* destination, const void* source, size_t count); // Fills |count| sequential bytes starting at |destination|, with the unsigned -// char coercion of |byte_value|. Behavior is undefined if |destination| is -// NULL. Returns |destination|, for some reason. Does nothing if |count| is 0. -// Meant to be a drop-in replacement for memset. +// char coercion of |byte_value|. This function is meant to be a drop-in +// replacement for |memset|. +// +// The function's behavior is undefined if |destination| is NULL, and the +// function is a no-op if |count| is 0. The return value is |destination|. +// +// |destination|: The destination of the copied memory. +// |count|: The number of sequential bytes to be set. SB_EXPORT void* SbMemorySet(void* destination, int byte_value, size_t count); // Compares the contents of the first |count| bytes of |buffer1| and |buffer2|. -// returns -1 if |buffer1| is "less-than" |buffer2|, 0 if they are equal, or 1 -// if |buffer1| is "greater-than" |buffer2|. Meant to be a drop-in replacement -// for memcmp. +// This function returns: +// - |-1| if |buffer1| is "less-than" |buffer2| +// - |0| if |buffer1| and |buffer2| are equal +// - |1| if |buffer1| is "greater-than" |buffer2|. +// +// This function is meant to be a drop-in replacement for |memcmp|. +// +// |buffer1|: The first buffer to be compared. +// |buffer2|: The second buffer to be compared. +// |count|: The number of bytes to be compared. SB_EXPORT int SbMemoryCompare(const void* buffer1, const void* buffer2, size_t count); -// Finds the lower 8-bits of |value| in the first |count| bytes of |buffer|, -// returning a pointer to the first found occurrence, or NULL if not -// found. Meant to be a drop-in replacement for memchr. +// Finds the lower 8-bits of |value| in the first |count| bytes of |buffer| +// and returns either a pointer to the first found occurrence or |NULL| if +// the value is not found. This function is meant to be a drop-in replacement +// for |memchr|. SB_EXPORT const void* SbMemoryFindByte(const void* buffer, int value, size_t count); -// A wrapper that implements a drop-in replacement for calloc, since some -// packages actually seem to use it. +// A wrapper that implements a drop-in replacement for |calloc|, which is used +// in some packages. static SB_C_INLINE void* SbMemoryCalloc(size_t count, size_t size) { size_t total = count * size; void* result = SbMemoryAllocate(total); @@ -206,6 +291,32 @@ return result; } +///////////////////////////////////////////////////////////////// +// Deprecated. Do not use. +///////////////////////////////////////////////////////////////// + +// Same as SbMemoryAllocateUnchecked, but will abort() in the case of an +// allocation failure. +// +// DO NOT CALL. Call SbMemoryAllocate(...) instead. +SB_DEPRECATED_EXTERNAL( + SB_EXPORT void* SbMemoryAllocateChecked(size_t size)); + +// Same as SbMemoryReallocateUnchecked, but will abort() in the case of an +// allocation failure. +// +// DO NOT CALL. Call SbMemoryReallocate(...) instead. +SB_DEPRECATED_EXTERNAL( + SB_EXPORT void* SbMemoryReallocateChecked(void* memory, size_t size)); + +// Same as SbMemoryAllocateAlignedUnchecked, but will abort() in the case of an +// allocation failure. +// +// DO NOT CALL. Call SbMemoryAllocateAligned(...) instead. +SB_DEPRECATED_EXTERNAL( + SB_EXPORT void* SbMemoryAllocateAlignedChecked(size_t alignment, + size_t size)); + #ifdef __cplusplus } // extern "C" #endif
diff --git a/src/starboard/memory_reporter.h b/src/starboard/memory_reporter.h new file mode 100644 index 0000000..1a17c38 --- /dev/null +++ b/src/starboard/memory_reporter.h
@@ -0,0 +1,105 @@ +// Copyright 2016 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. + +// Module Overview: Memory Reporting API. +// +// Provides an interface for memory reporting. + +#ifndef STARBOARD_MEMORY_REPORTER_H_ +#define STARBOARD_MEMORY_REPORTER_H_ + +#include "starboard/configuration.h" +#include "starboard/export.h" +#include "starboard/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +// These are callbacks used by SbMemoryReporter to report memory +// allocation and deallocation. +/////////////////////////////////////////////////////////////////////////////// + +// A function to report a memory allocation from SbMemoryAllocate(). Note that +// operator new calls SbMemoryAllocate which will delegate to this callback. +typedef void (*SbMemoryReporterOnAlloc)(void* context, const void* memory, + size_t size); + +// A function to report a memory deallocation from SbMemoryDeallcoate(). Note +// that operator delete calls SbMemoryDeallocate which will delegate to this +// callback. +typedef void (*SbMemoryReporterOnDealloc)(void* context, const void* memory); + +// A function to report a memory mapping from SbMemoryMap(). +typedef void (*SbMemoryReporterOnMapMemory)(void* context, const void* memory, + size_t size); + +// A function to report a memory unmapping from SbMemoryUnmap(). +typedef void (*SbMemoryReporterOnUnMapMemory)(void* context, + const void* memory, + size_t size); + +// SbMemoryReporter allows memory reporting via user-supplied functions. +// The void* context is passed to every call back. +// It's strongly recommended that C-Style struct initialization is used +// so that the arguments can be typed check by the compiler. +// For example, +// SbMemoryReporter mem_reporter = { MallocCallback, .... context }; +struct SbMemoryReporter { + // Callback to report allocations. + SbMemoryReporterOnAlloc on_alloc_cb; + + // Callback to report deallocations. + SbMemoryReporterOnDealloc on_dealloc_cb; + + // Callback to report memory map. + SbMemoryReporterOnMapMemory on_mapmem_cb; + + // Callback to report memory unmap. + SbMemoryReporterOnUnMapMemory on_unmapmem_cb; + + // Optional, is passed to callbacks as first argument. + void* context; +}; + +// Sets the MemoryReporter. Any previous memory reporter is unset. No lifetime +// management is done internally on input pointer. +// +// Returns true if the memory reporter was set with no errors. If an error +// was reported then check the log for why it failed. +// +// Note that other than a thread-barrier-write of the input pointer, there is +// no thread safety guarantees with this function due to performance +// considerations. It's recommended that this be called once during the +// lifetime of the program, or not at all. Do not delete the supplied pointer, +// ever. +// Example (Good): +// SbMemoryReporter* mem_reporter = new ...; +// SbMemorySetReporter(&mem_reporter); +// ... +// SbMemorySetReporter(NULL); // allow value to leak. +// Example (Bad): +// SbMemoryReporter* mem_reporter = new ...; +// SbMemorySetReporter(&mem_reporter); +// ... +// SbMemorySetReporter(NULL); +// delete mem_reporter; // May crash. +SB_EXPORT bool SbMemorySetReporter(struct SbMemoryReporter* tracker); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // STARBOARD_MEMORY_REPORTER_H_
diff --git a/src/starboard/microphone.h b/src/starboard/microphone.h new file mode 100644 index 0000000..52464c3 --- /dev/null +++ b/src/starboard/microphone.h
@@ -0,0 +1,199 @@ +// Copyright 2016 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. + +// Module Overview: Starboard Microphone module +// +// Defines functions for microphone creation, control, audio data fetching, +// and destruction. This module supports multiple calls to |SbMicrophoneOpen| +// and |SbMicrophoneClose|, and the implementation should handle multiple calls +// to one of those functions on the same microphone. For example, your +// implementation should handle cases where |SbMicrophoneOpen| is called twice +// on the same microphone without a call to |SbMicrophoneClose| in between. +// +// This API is not thread-safe and must be called from a single thread. +// +// How to use this API: +// 1. Call |SbMicrophoneGetAvailableInfos| to get a list of available microphone +// information. +// 2. Create a supported microphone, using |SbMicrophoneCreate|, with enough +// buffer size and sample rate. Use |SbMicrophoneIsSampleRateSupported| to +// verify the sample rate. +// 3. Use |SbMicrophoneOpen| to open the microphone port and start recording +// audio data. +// 4. Periodically read out the data from microphone with |SbMicrophoneRead|. +// 5. Call |SbMicrophoneClose| to close the microphone port and stop recording +// audio data. +// 6. Destroy the microphone with |SbMicrophoneDestroy|. + +#ifndef STARBOARD_MICROPHONE_H_ +#define STARBOARD_MICROPHONE_H_ + +#include "starboard/configuration.h" +#include "starboard/export.h" +#include "starboard/types.h" + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +#ifdef __cplusplus +extern "C" { +#endif + +// All possible microphone types. +typedef enum SbMicrophoneType { + // Built-in microphone in camera. + kSbMicrophoneCamera, + + // Microphone in the headset that can be a wired or wireless USB headset. + kSbMicrophoneUSBHeadset, + + // Microphone in the VR headset. + kSbMicrophoneVRHeadset, + + // Microphone in the analog headset. + kSBMicrophoneAnalogHeadset, + + // Unknown microphone type. The microphone could be different than the other + // enum descriptions or could fall under one of those descriptions. + kSbMicrophoneUnknown, +} SbMicrophoneType; + +// An opaque handle to an implementation-private structure that represents a +// microphone ID. +typedef struct SbMicrophoneIdPrivate* SbMicrophoneId; + +// Well-defined value for an invalid microphone ID handle. +#define kSbMicrophoneIdInvalid ((SbMicrophoneId)NULL) + +// Indicates whether the given microphone ID is valid. +static SB_C_INLINE bool SbMicrophoneIdIsValid(SbMicrophoneId id) { + return id != kSbMicrophoneIdInvalid; +} + +// Microphone information. +typedef struct SbMicrophoneInfo { + // Microphone id. + SbMicrophoneId id; + + // Microphone type. + SbMicrophoneType type; + + // The microphone's maximum supported sampling rate. + int max_sample_rate_hz; + + // The minimum read size required for each read from microphone. + int min_read_size; +} SbMicrophoneInfo; + +// An opaque handle to an implementation-private structure that represents a +// microphone. +typedef struct SbMicrophonePrivate* SbMicrophone; + +// Well-defined value for an invalid microphone handle. +#define kSbMicrophoneInvalid ((SbMicrophone)NULL) + +// Indicates whether the given microphone is valid. +static SB_C_INLINE bool SbMicrophoneIsValid(SbMicrophone microphone) { + return microphone != kSbMicrophoneInvalid; +} + +// Retrieves all currently available microphone information and stores it in +// |out_info_array|. The return value is the number of the available +// microphones. If the number of available microphones is larger than +// |info_array_size|, then |out_info_array| is filled up with as many available +// microphones as possible and the actual number of available microphones is +// returned. A negative return value indicates that an internal error occurred. +// +// |out_info_array|: All currently available information about the microphone +// is placed into this output parameter. +// |info_array_size|: The size of |out_info_array|. +SB_EXPORT int SbMicrophoneGetAvailable(SbMicrophoneInfo* out_info_array, + int info_array_size); + +// Indicates whether the microphone supports the sample rate. +SB_EXPORT bool SbMicrophoneIsSampleRateSupported(SbMicrophoneId id, + int sample_rate_in_hz); + +// Creates a microphone with the specified ID, audio sample rate, and cached +// audio buffer size. Starboard only requires support for creating one +// microphone at a time, and implementations may return an error if a second +// microphone is created before the first is destroyed. +// +// The function returns the newly created SbMicrophone object. However, if you +// try to create a microphone that has already been initialized, if the sample +// rate is unavailable, or if the buffer size is invalid, the function should +// return |kSbMicrophoneInvalid|. +// +// |id|: The ID that will be assigned to the newly created SbMicrophone. +// |sample_rate_in_hz|: The new microphone's audio sample rate in Hz. +// |buffer_size_bytes|: The size of the buffer where signed 16-bit integer +// audio data is temporarily cached to during the capturing. The audio data +// is removed from the audio buffer if it has been read, and new audio data +// can be read from this buffer in smaller chunks than this size. This +// parameter must be set to a value greater than zero and the ideal size is +// |2^n|. +SB_EXPORT SbMicrophone SbMicrophoneCreate(SbMicrophoneId id, + int sample_rate_in_hz, + int buffer_size_bytes); + +// Opens the microphone port and starts recording audio on |microphone|. +// +// Once started, the client needs to periodically call |SbMicrophoneRead| to +// receive the audio data. If the microphone has already been started, this call +// clears the unread buffer. The return value indicates whether the microphone +// is open. +// |microphone|: The microphone that will be opened and will start recording +// audio. +SB_EXPORT bool SbMicrophoneOpen(SbMicrophone microphone); + +// Closes the microphone port, stops recording audio on |microphone|, and +// clears the unread buffer if it is not empty. If the microphone has already +// been stopped, this call is ignored. The return value indicates whether the +// microphone is closed. +// +// |microphone|: The microphone to close. +SB_EXPORT bool SbMicrophoneClose(SbMicrophone microphone); + +// Retrieves the recorded audio data from the microphone and writes that data +// to |out_audio_data|. +// +// The return value is zero or the positive number of bytes that were read. +// Neither the return value nor |audio_data_size| exceeds the buffer size. +// A negative return value indicates that an error occurred. +// +// This function should be called frequently. Otherwise, the microphone only +// buffers |buffer_size| bytes as configured in |SbMicrophoneCreate| and the +// new audio data is thrown out. No audio data is read from a stopped +// microphone. +// +// |microphone|: The microphone from which to retrieve recorded audio data. +// |out_audio_data|: The buffer to which the retrieved data will be written. +// |audio_data_size|: The number of requested bytes. If |audio_data_size| is +// smaller than |min_read_size| of |SbMicrophoneInfo|, the extra audio data +// that has already been read from the device is discarded. +SB_EXPORT int SbMicrophoneRead(SbMicrophone microphone, + void* out_audio_data, + int audio_data_size); + +// Destroys a microphone. If the microphone is in started state, it is first +// stopped and then destroyed. Any data that has been recorded and not read +// is thrown away. +SB_EXPORT void SbMicrophoneDestroy(SbMicrophone microphone); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +#endif // STARBOARD_MICROPHONE_H_
diff --git a/src/starboard/mutex.h b/src/starboard/mutex.h index d06c752..953fa0d 100644 --- a/src/starboard/mutex.h +++ b/src/starboard/mutex.h
@@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// A mutually exclusive lock that can be used to coordinate with other threads. +// Module Overview: Starboard Mutex module +// +// Defines a mutually exclusive lock that can be used to coordinate with other +// threads. #ifndef STARBOARD_MUTEX_H_ #define STARBOARD_MUTEX_H_ @@ -37,31 +40,46 @@ kSbMutexDestroyed, } SbMutexResult; -// Returns whether the given result is a success. +// Indicates whether the given result is a success. A value of |true| indicates +// that the mutex was acquired. +// +// |result|: The result being checked. static SB_C_FORCE_INLINE bool SbMutexIsSuccess(SbMutexResult result) { return result == kSbMutexAcquired; } -// Creates a new mutex, placing the handle to the newly created mutex in -// |out_mutex|. Returns whether able to create a new mutex. +// Creates a new mutex. The return value indicates whether the function +// was able to create a new mutex. +// +// |out_mutex|: The handle to the newly created mutex. SB_EXPORT bool SbMutexCreate(SbMutex* out_mutex); -// Destroys a mutex, returning whether the destruction was successful. The mutex -// specified by |mutex| is invalidated. +// Destroys a mutex. The return value indicates whether the destruction was +// successful. +// +// |mutex|: The mutex to be invalidated. SB_EXPORT bool SbMutexDestroy(SbMutex* mutex); -// Acquires |mutex|, blocking indefinitely, returning the acquisition result. -// SbMutexes are not reentrant, so a recursive acquisition will block forever. +// Acquires |mutex|, blocking indefinitely. The return value identifies +// the acquisition result. SbMutexes are not reentrant, so a recursive +// acquisition blocks forever. +// +// |mutex|: The mutex to be acquired. SB_EXPORT SbMutexResult SbMutexAcquire(SbMutex* mutex); -// Acquires |mutex|, without blocking, returning the acquisition result. -// SbMutexes are not reentrant, so a recursive acquisition will always fail. +// 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. SB_EXPORT SbMutexResult SbMutexAcquireTry(SbMutex* mutex); -// Releases |mutex| held by the current thread, returning whether the release -// was successful. Releases should always be successful if the mutex is held by -// the current thread. -SB_EXPORT bool SbMutexRelease(SbMutex* handle); +// Releases |mutex| held by the current thread. The return value indicates +// whether the release was successful. Releases should always be successful +// if |mutex| is held by the current thread. +// +// |mutex|: The mutex to be released. +SB_EXPORT bool SbMutexRelease(SbMutex* mutex); #ifdef __cplusplus } // extern "C"
diff --git a/src/starboard/nplb/blitter_flush_context_test.cc b/src/starboard/nplb/blitter_flush_context_test.cc index 1095b90..4707e10 100644 --- a/src/starboard/nplb/blitter_flush_context_test.cc +++ b/src/starboard/nplb/blitter_flush_context_test.cc
@@ -67,6 +67,7 @@ // Check that flush succeeds after some commands have been submitted. EXPECT_TRUE(SbBlitterFlushContext(context)); + EXPECT_TRUE(SbBlitterDestroySurface(surface)); EXPECT_TRUE(SbBlitterDestroyContext(context)); EXPECT_TRUE(SbBlitterDestroyDevice(device)); }
diff --git a/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectInterpolated-expected.png b/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectInterpolated-expected.png new file mode 100644 index 0000000..27424c4 --- /dev/null +++ b/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectInterpolated-expected.png Binary files differ
diff --git a/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRect-expected.png b/src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectNotInterpolated-expected.png similarity index 100% rename from src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRect-expected.png rename to src/starboard/nplb/blitter_pixel_tests/data/MagnifyBlitRectToRectNotInterpolated-expected.png Binary files differ
diff --git a/src/starboard/nplb/blitter_pixel_tests/tests.cc b/src/starboard/nplb/blitter_pixel_tests/tests.cc index 871c074..9554da7 100644 --- a/src/starboard/nplb/blitter_pixel_tests/tests.cc +++ b/src/starboard/nplb/blitter_pixel_tests/tests.cc
@@ -99,9 +99,15 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } -TEST_F(SbBlitterPixelTest, MagnifyBlitRectToRect) { +#if SB_HAS(BILINEAR_FILTERING_SUPPORT) +TEST_F(SbBlitterPixelTest, MagnifyBlitRectToRectInterpolated) { +#else +TEST_F(SbBlitterPixelTest, MagnifyBlitRectToRectNotInterpolated) { +#endif // Create an image with a height and width of 2x2. SbBlitterSurface checker_image = CreateCheckerImageWithBlits( device_, context_, SbBlitterColorFromRGBA(255, 255, 255, 255), @@ -112,6 +118,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, 2, 2), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, MinifyBlitRectToRect) { @@ -124,6 +132,8 @@ SbBlitterBlitRectToRect( context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth() / 8, GetHeight() / 8)); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitRectToRectPartialSourceRect) { @@ -137,6 +147,8 @@ context_, checker_image, SbBlitterMakeRect(GetWidth() / 2, 0, GetWidth() / 2, GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitRectToRectTiled) { @@ -149,6 +161,8 @@ SbBlitterBlitRectToRectTiled( context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitRectToRectTiledWithNoTiling) { @@ -161,6 +175,8 @@ SbBlitterBlitRectToRectTiled( context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitRectToRectTiledNegativeOffset) { @@ -174,6 +190,8 @@ context_, checker_image, SbBlitterMakeRect(-GetWidth(), -GetHeight(), GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitRectToRectTiledOffCenter) { @@ -188,6 +206,8 @@ SbBlitterMakeRect(-GetWidth() / 2, -GetHeight() / 2, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } void DrawRectsWithBatchBlit(SbBlitterSurface texture, @@ -221,6 +241,8 @@ SbBlitterSetRenderTarget(context_, render_target_); DrawRectsWithBatchBlit(checker_image, context_, GetWidth(), GetHeight()); + + SbBlitterDestroySurface(checker_image); } SbBlitterSurface CreateCheckerImageWithPixelData(SbBlitterDevice device, @@ -329,6 +351,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitRedGreenRectToRectFromPixelData) { @@ -339,6 +363,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitHalfTransparentRectToRectFromPixelData) { @@ -349,6 +375,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitCanPunchThrough) { @@ -364,6 +392,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlitCanBlend) { @@ -379,6 +409,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, SimpleAlphaBlitWithNoColorModulation) { @@ -393,6 +425,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlendedAlphaBlitWithNoColorModulationOnRed) { @@ -406,6 +440,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, AlphaBlitWithBlueColorModulation) { @@ -418,6 +454,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlendedAlphaBlitWithBlueColorModulationOnRed) { @@ -432,6 +470,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlendedAlphaBlitWithAlphaColorModulationOnRed) { @@ -446,6 +486,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlendedColorBlitWithAlphaColorModulationOnRed) { @@ -461,6 +503,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, BlendedColorBlitWithAlphaColorModulation) { @@ -474,6 +518,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, FillRectColorIsNotPremultiplied) { @@ -508,6 +554,8 @@ SbBlitterBlitRectToRect(context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth(), GetHeight()), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, ScissoredBlitRectToRectTiled) { @@ -525,6 +573,8 @@ context_, checker_image, SbBlitterMakeRect(0, 0, GetWidth() * 2, GetHeight() * 2), SbBlitterMakeRect(0, 0, GetWidth(), GetHeight())); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, ScissoredBlitRectToRects) { @@ -539,6 +589,8 @@ SbBlitterSetScissor( context_, SbBlitterMakeRect(32, 32, GetWidth() - 48, GetHeight() - 48)); DrawRectsWithBatchBlit(checker_image, context_, GetWidth(), GetHeight()); + + SbBlitterDestroySurface(checker_image); } TEST_F(SbBlitterPixelTest, ScissorResetsWhenSetRenderTargetIsCalled) {
diff --git a/src/starboard/nplb/decode_target_create_test.cc b/src/starboard/nplb/decode_target_create_test.cc new file mode 100644 index 0000000..287dc21 --- /dev/null +++ b/src/starboard/nplb/decode_target_create_test.cc
@@ -0,0 +1,193 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/window.h" +#include "testing/gtest/include/gtest/gtest.h" + +// This must come after gtest, because it includes GL, which can include X11, +// which will define None to be 0L, which conflicts with gtest. +#include "starboard/decode_target.h" // NOLINT(build/include_order) + +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + +#if SB_HAS(BLITTER) +#include "starboard/blitter.h" +#include "starboard/nplb/blitter_helpers.h" +#endif + +namespace starboard { +namespace nplb { +namespace { + +#if SB_HAS(BLITTER) +const int kWidth = 128; +const int kHeight = 128; + +TEST(SbDecodeTargetTest, SunnyDayCreate) { + ContextTestEnvironment env(kWidth, kHeight); + + ASSERT_TRUE(SbBlitterSetRenderTarget(env.context(), env.render_target())); + + SbBlitterSurface surface = + CreateArbitraryRenderTargetSurface(env.device(), kWidth, kHeight); + + SbDecodeTarget target = + SbDecodeTargetCreate(kSbDecodeTargetFormat1PlaneRGBA, &surface); + if (SbDecodeTargetIsValid(target)) { + SbBlitterSurface plane = + SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA); + EXPECT_TRUE(SbBlitterIsSurfaceValid(plane)); + } + SbDecodeTargetDestroy(target); + EXPECT_TRUE(SbBlitterDestroySurface(surface)); +} +#elif SB_HAS(GLES2) // SB_HAS(BLITTER) +// clang-format off +EGLint const kAttributeList[] = { + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_STENCIL_SIZE, 0, + EGL_BUFFER_SIZE, 32, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, + EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER, + EGL_CONFORMANT, EGL_OPENGL_ES2_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE, +}; +// clang-format on + +class SbDecodeTargetTest : public testing::Test { + protected: + void SetUp() SB_OVERRIDE { + SbWindowOptions options; + SbWindowSetDefaultOptions(&options); + window_ = SbWindowCreate(&options); + + display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_DISPLAY, display_); + + eglInitialize(display_, NULL, NULL); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + EGLint num_configs = 0; + eglChooseConfig(display_, kAttributeList, NULL, 0, &num_configs); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(0, num_configs); + + // Allocate space to receive the matching configs and retrieve them. + EGLConfig* configs = new EGLConfig[num_configs]; + eglChooseConfig(display_, kAttributeList, configs, num_configs, + &num_configs); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + + EGLNativeWindowType native_window = + (EGLNativeWindowType)SbWindowGetPlatformHandle(window_); + EGLConfig config; + + // Find the first config that successfully allow a window surface to be + // created. + surface_ = EGL_NO_SURFACE; + for (int config_number = 0; config_number < num_configs; ++config_number) { + config = configs[config_number]; + surface_ = eglCreateWindowSurface(display_, config, native_window, NULL); + if (EGL_SUCCESS == eglGetError()) + break; + } + ASSERT_NE(EGL_NO_SURFACE, surface_); + + delete[] configs; + + // Create the GLES2 or GLEX3 Context. + context_ = EGL_NO_CONTEXT; + EGLint context_attrib_list[] = { + EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE, + }; +#if defined(GLES3_SUPPORTED) + // Attempt to create an OpenGL ES 3.0 context. + context_ = + eglCreateContext(display_, config, EGL_NO_CONTEXT, context_attrib_list); +#endif + if (context_ == EGL_NO_CONTEXT) { + // Create an OpenGL ES 2.0 context. + context_attrib_list[1] = 2; + context_ = eglCreateContext(display_, config, EGL_NO_CONTEXT, + context_attrib_list); + } + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + ASSERT_NE(EGL_NO_CONTEXT, context_); + + // connect the context to the surface + eglMakeCurrent(display_, surface_, surface_, context_); + ASSERT_EQ(EGL_SUCCESS, eglGetError()); + } + + void TearDown() SB_OVERRIDE { + eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + EXPECT_EQ(EGL_SUCCESS, eglGetError()); + eglDestroyContext(display_, context_); + EXPECT_EQ(EGL_SUCCESS, eglGetError()); + eglDestroySurface(display_, surface_); + EXPECT_EQ(EGL_SUCCESS, eglGetError()); + eglTerminate(display_); + EXPECT_EQ(EGL_SUCCESS, eglGetError()); + SbWindowDestroy(window_); + } + + EGLContext context_; + EGLDisplay display_; + EGLSurface surface_; + SbWindow window_; +}; + +TEST_F(SbDecodeTargetTest, SunnyDayCreate) { + // Generate a texture to put in the SbDecodeTarget. + GLuint texture_handle; + glGenTextures(1, &texture_handle); + glBindTexture(GL_TEXTURE_2D, texture_handle); + glTexImage2D(GL_TEXTURE_2D, 0 /*level*/, GL_RGBA, 256, 256, 0 /*border*/, + GL_RGBA, GL_UNSIGNED_BYTE, NULL /*data*/); + glBindTexture(GL_TEXTURE_2D, 0); + + SbDecodeTarget target = SbDecodeTargetCreate( + display_, context_, kSbDecodeTargetFormat1PlaneRGBA, &texture_handle); + if (SbDecodeTargetIsValid(target)) { + GLuint plane = SbDecodeTargetGetPlane(target, kSbDecodeTargetPlaneRGBA); + EXPECT_EQ(texture_handle, plane); + SbDecodeTargetDestroy(target); + } + glDeleteTextures(1, &texture_handle); +} + +#else // SB_HAS(BLITTER) + +TEST(SbDecodeTargetTest, SunnyDayCreate) { + // When graphics are not enabled, we expect to always create a + // kSbDecodeTargetInvalid, and get NULL back for planes. + EXPECT_EQ(SbDecodeTargetCreate(kSbDecodeTargetFormat1PlaneRGBA), + kSbDecodeTargetInvalid); + EXPECT_EQ( + SbDecodeTargetGetPlane(kSbDecodeTargetInvalid, kSbDecodeTargetPlaneRGBA), + NULL); +} + +#endif // SB_HAS(BLITTER) + +} // namespace +} // namespace nplb +} // namespace starboard + +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS)
diff --git a/src/starboard/nplb/decode_target_provider_test.cc b/src/starboard/nplb/decode_target_provider_test.cc new file mode 100644 index 0000000..cd53e40 --- /dev/null +++ b/src/starboard/nplb/decode_target_provider_test.cc
@@ -0,0 +1,154 @@ +// Copyright 2016 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 "testing/gtest/include/gtest/gtest.h" + +// This must come after gtest, because it includes GL, which can include X11, +// which will define None to be 0L, which conflicts with gtest. +#include "starboard/decode_target.h" // NOLINT(build/include_order) + +#if SB_VERSION(3) && SB_HAS(GRAPHICS) + +namespace starboard { +namespace nplb { +namespace { + +struct ProviderContext { + static SbDecodeTarget Acquire(void* raw_context, + SbDecodeTargetFormat format, + int width, + int height) { + ProviderContext* context = reinterpret_cast<ProviderContext*>(raw_context); + ++context->called_acquire; + return kSbDecodeTargetInvalid; + } + + static void Release(void* raw_context, SbDecodeTarget target) { + ProviderContext* context = reinterpret_cast<ProviderContext*>(raw_context); + ++context->called_release; + } + + int called_acquire; + int called_release; +}; + +void AcquireFalse(SbDecodeTargetProvider* provider) { + if (provider) { + SbDecodeTarget target = SbDecodeTargetAcquireFromProvider( + provider, kSbDecodeTargetFormat1PlaneRGBA, 16, 16); + bool valid = SbDecodeTargetIsValid(target); + EXPECT_FALSE(valid); + if (valid) { + SbDecodeTargetDestroy(target); + } + } +} + +void ReleaseInvalid(SbDecodeTargetProvider* provider) { + if (provider) { + SbDecodeTargetReleaseToProvider(provider, kSbDecodeTargetInvalid); + } +} + +TEST(SbDecodeTargetProviderTest, SunnyDayCallsThroughToProvider) { + ProviderContext context = {}; + SbDecodeTargetProvider provider = {&ProviderContext::Acquire, + &ProviderContext::Release, + reinterpret_cast<void*>(&context)}; + + EXPECT_EQ(0, context.called_acquire); + EXPECT_EQ(0, context.called_release); + + AcquireFalse(&provider); + EXPECT_EQ(1, context.called_acquire); + AcquireFalse(&provider); + EXPECT_EQ(2, context.called_acquire); + + ReleaseInvalid(&provider); + EXPECT_EQ(1, context.called_release); + ReleaseInvalid(&provider); + EXPECT_EQ(2, context.called_release); +} + +TEST(SbDecodeTargetProviderTest, SunnyDayMultipleProviders) { + ProviderContext context = {}; + SbDecodeTargetProvider provider = {&ProviderContext::Acquire, + &ProviderContext::Release, + reinterpret_cast<void*>(&context)}; + + ProviderContext context2 = {}; + SbDecodeTargetProvider provider2 = {&ProviderContext::Acquire, + &ProviderContext::Release, + reinterpret_cast<void*>(&context2)}; + + AcquireFalse(&provider2); + EXPECT_EQ(1, context2.called_acquire); + EXPECT_EQ(0, context.called_acquire); + AcquireFalse(&provider2); + EXPECT_EQ(2, context2.called_acquire); + EXPECT_EQ(0, context.called_acquire); + + ReleaseInvalid(&provider2); + EXPECT_EQ(1, context2.called_release); + EXPECT_EQ(0, context.called_release); + ReleaseInvalid(&provider2); + EXPECT_EQ(2, context2.called_release); + EXPECT_EQ(0, context.called_release); +} + +TEST(SbDecodeTargetProviderTest, RainyDayAcquireNoProvider) { + AcquireFalse(NULL); +} + +TEST(SbDecodeTargetProviderTest, RainyDayReleaseNoProvider) { + ReleaseInvalid(NULL); +} + +TEST(SbDecodeTargetProviderTest, RainyDayMultipleProviders) { + ProviderContext context = {}; + SbDecodeTargetProvider provider = {&ProviderContext::Acquire, + &ProviderContext::Release, + reinterpret_cast<void*>(&context)}; + + ProviderContext context2 = {}; + SbDecodeTargetProvider provider2 = {&ProviderContext::Acquire, + &ProviderContext::Release, + reinterpret_cast<void*>(&context2)}; + + AcquireFalse(&provider2); + EXPECT_EQ(1, context2.called_acquire); + AcquireFalse(&provider2); + EXPECT_EQ(2, context2.called_acquire); + + ReleaseInvalid(&provider2); + EXPECT_EQ(1, context2.called_release); + ReleaseInvalid(&provider2); + EXPECT_EQ(2, context2.called_release); + + AcquireFalse(&provider); + EXPECT_EQ(2, context2.called_acquire); + AcquireFalse(&provider); + EXPECT_EQ(2, context2.called_acquire); + + ReleaseInvalid(&provider); + EXPECT_EQ(2, context2.called_release); + ReleaseInvalid(&provider); + EXPECT_EQ(2, context2.called_release); +} + +} // namespace +} // namespace nplb +} // namespace starboard + +#endif // SB_VERSION(3) && SB_HAS(GRAPHICS)
diff --git a/src/starboard/nplb/file_get_info_test.cc b/src/starboard/nplb/file_get_info_test.cc index 1722577..fa61d8b 100644 --- a/src/starboard/nplb/file_get_info_test.cc +++ b/src/starboard/nplb/file_get_info_test.cc
@@ -40,6 +40,11 @@ // Assuming platforms have at least 1 second precision on filesystem // timestamps, we need to go back two seconds to avoid rounding issues. SbTime time = SbTimeGetNow() - (2 * kSbTimeSecond); +#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME) + // On platforms with coarse access time, we assume 1 day precision and go + // back 2 days to avoid rounding issues. + SbTime coarse_time = SbTimeGetNow() - (2 * kSbTimeDay); +#endif const int kFileSize = 12; starboard::nplb::ScopedRandomFile random_file(kFileSize); @@ -56,7 +61,11 @@ EXPECT_FALSE(info.is_directory); EXPECT_FALSE(info.is_symbolic_link); EXPECT_LE(time, info.last_modified); +#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME) + EXPECT_LE(coarse_time, info.last_accessed); +#else EXPECT_LE(time, info.last_accessed); +#endif EXPECT_LE(time, info.creation_time); } @@ -65,30 +74,6 @@ } } -TEST(SbFileGetInfoTest, WorksOnADirectory) { - char path[SB_FILE_MAX_PATH] = {0}; - bool result = - SbSystemGetPath(kSbSystemPathTempDirectory, path, SB_FILE_MAX_PATH); - EXPECT_TRUE(result); - - SbFile file = SbFileOpen(path, kSbFileOpenOnly | kSbFileRead, NULL, NULL); - ASSERT_TRUE(SbFileIsValid(file)); - - { - SbFileInfo info = {0}; - bool result = SbFileGetInfo(file, &info); - EXPECT_LE(0, info.size); - EXPECT_TRUE(info.is_directory); - EXPECT_FALSE(info.is_symbolic_link); - EXPECT_LE(0, info.last_modified); - EXPECT_LE(0, info.last_accessed); - EXPECT_LE(0, info.creation_time); - } - - result = SbFileClose(file); - EXPECT_TRUE(result); -} - } // namespace } // namespace nplb } // namespace starboard
diff --git a/src/starboard/nplb/file_get_path_info_test.cc b/src/starboard/nplb/file_get_path_info_test.cc index f96e205..fc024ff 100644 --- a/src/starboard/nplb/file_get_path_info_test.cc +++ b/src/starboard/nplb/file_get_path_info_test.cc
@@ -52,6 +52,11 @@ // Assuming platforms have at least 1 second precision on filesystem // timestamps, we need to go back two seconds to avoid rounding issues. SbTime time = SbTimeGetNow() - (2 * kSbTimeSecond); +#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME) + // On platforms with coarse access time, we assume 1 day precision and go + // back 2 days to avoid rounding issues. + SbTime coarse_time = SbTimeGetNow() - (2 * kSbTimeDay); +#endif const int kFileSize = 12; ScopedRandomFile random_file(kFileSize); @@ -64,7 +69,11 @@ EXPECT_FALSE(info.is_directory); EXPECT_FALSE(info.is_symbolic_link); EXPECT_LE(time, info.last_modified); +#if SB_HAS_QUIRK(FILESYSTEM_COARSE_ACCESS_TIME) + EXPECT_LE(coarse_time, info.last_accessed); +#else EXPECT_LE(time, info.last_accessed); +#endif EXPECT_LE(time, info.creation_time); } }
diff --git a/src/starboard/nplb/file_read_test.cc b/src/starboard/nplb/file_read_test.cc index e294bca..a4c828d 100644 --- a/src/starboard/nplb/file_read_test.cc +++ b/src/starboard/nplb/file_read_test.cc
@@ -22,17 +22,39 @@ namespace nplb { namespace { +// Sets up an empty test fixture, required for typed tests. +template <class SbFileReadType> +class SbFileReadTest : public testing::Test {}; + +class SbFileReader { + public: + static int Read(SbFile file, char* data, int size) { + return SbFileRead(file, data, size); + } +}; + +class SbFileReaderAll { + public: + static int Read(SbFile file, char* data, int size) { + return SbFileReadAll(file, data, size); + } +}; + +typedef testing::Types<SbFileReader, SbFileReaderAll> SbFileReadTestTypes; + +TYPED_TEST_CASE(SbFileReadTest, SbFileReadTestTypes); + const int kBufferLength = 16 * 1024; -TEST(SbFileReadTest, InvalidFileErrors) { +TYPED_TEST(SbFileReadTest, InvalidFileErrors) { char buffer[kBufferLength]; - int result = SbFileRead(kSbFileInvalid, buffer, kBufferLength); + int result = TypeParam::Read(kSbFileInvalid, buffer, kBufferLength); EXPECT_EQ(-1, result); } -TEST(SbFileReadTest, BasicReading) { - // Create a pattern file that is not an even multiple of the buffer size, but - // is over several times the size of the buffer. +TYPED_TEST(SbFileReadTest, BasicReading) { + // Create a pattern file that is not an even multiple of the buffer size, + // but is over several times the size of the buffer. const int kFileSize = kBufferLength * 16 / 3; ScopedRandomFile random_file(kFileSize); const std::string& filename = random_file.filename(); @@ -41,8 +63,8 @@ SbFileOpen(filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL); ASSERT_TRUE(SbFileIsValid(file)); - // Create a bigger buffer than necessary, so we can test the memory around the - // portion given to SbFileRead. + // Create a bigger buffer than necessary, so we can test the memory around + // the portion given to SbFileRead. const int kRealBufferLength = kBufferLength * 2; char real_buffer[kRealBufferLength] = {0}; const int kBufferOffset = kBufferLength / 2; @@ -58,7 +80,7 @@ int previous_total = 0; int max = 0; while (true) { - int bytes_read = SbFileRead(file, buffer, kBufferLength); + int bytes_read = TypeParam::Read(file, buffer, kBufferLength); if (bytes_read == 0) { break; } @@ -96,7 +118,7 @@ EXPECT_TRUE(result); } -TEST(SbFileReadTest, ReadPastEnd) { +TYPED_TEST(SbFileReadTest, ReadPastEnd) { const int kFileSize = kBufferLength; ScopedRandomFile random_file(kFileSize); const std::string& filename = random_file.filename(); @@ -105,8 +127,8 @@ SbFileOpen(filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL); ASSERT_TRUE(SbFileIsValid(file)); - // Create a bigger buffer than necessary, so we can test the memory around the - // portion given to SbFileRead. + // Create a bigger buffer than necessary, so we can test the memory around + // the portion given to SbFileRead. const int kRealBufferLength = kBufferLength * 2; char real_buffer[kRealBufferLength] = {0}; const int kBufferOffset = kBufferLength / 2; @@ -120,7 +142,7 @@ // Read off the end of the file. int position = SbFileSeek(file, kSbFileFromEnd, 0); EXPECT_EQ(kFileSize, position); - int bytes_read = SbFileRead(file, buffer, kBufferLength); + int bytes_read = TypeParam::Read(file, buffer, kBufferLength); EXPECT_EQ(0, bytes_read); for (int i = 0; i < kRealBufferLength; ++i) { @@ -131,7 +153,7 @@ EXPECT_TRUE(result); } -TEST(SbFileReadTest, ReadZeroBytes) { +TYPED_TEST(SbFileReadTest, ReadZeroBytes) { const int kFileSize = kBufferLength; ScopedRandomFile random_file(kFileSize); const std::string& filename = random_file.filename(); @@ -140,8 +162,8 @@ SbFileOpen(filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL); ASSERT_TRUE(SbFileIsValid(file)); - // Create a bigger buffer than necessary, so we can test the memory around the - // portion given to SbFileRead. + // Create a bigger buffer than necessary, so we can test the memory around + // the portion given to SbFileRead. const int kRealBufferLength = kBufferLength * 2; char real_buffer[kRealBufferLength] = {0}; const int kBufferOffset = kBufferLength / 2; @@ -154,7 +176,7 @@ // Read zero bytes. for (int i = 0; i < 10; ++i) { - int bytes_read = SbFileRead(file, buffer, 0); + int bytes_read = TypeParam::Read(file, buffer, 0); EXPECT_EQ(0, bytes_read); } @@ -166,7 +188,7 @@ EXPECT_TRUE(result); } -TEST(SbFileReadTest, ReadFromMiddle) { +TYPED_TEST(SbFileReadTest, ReadFromMiddle) { const int kFileSize = kBufferLength * 2; ScopedRandomFile random_file(kFileSize); const std::string& filename = random_file.filename(); @@ -175,8 +197,8 @@ SbFileOpen(filename.c_str(), kSbFileOpenOnly | kSbFileRead, NULL, NULL); ASSERT_TRUE(SbFileIsValid(file)); - // Create a bigger buffer than necessary, so we can test the memory around the - // portion given to SbFileRead. + // Create a bigger buffer than necessary, so we can test the memory around + // the portion given to SbFileRead. const int kRealBufferLength = kBufferLength * 2; char real_buffer[kRealBufferLength] = {0}; const int kBufferOffset = kBufferLength / 2; @@ -190,7 +212,7 @@ // Read from the middle of the file. int position = SbFileSeek(file, kSbFileFromBegin, kFileSize / 4); EXPECT_EQ(kFileSize / 4, position); - int bytes_read = SbFileRead(file, buffer, kBufferLength); + int bytes_read = TypeParam::Read(file, buffer, kBufferLength); EXPECT_GE(kBufferLength, bytes_read); EXPECT_LT(0, bytes_read);
diff --git a/src/starboard/nplb/file_write_test.cc b/src/starboard/nplb/file_write_test.cc index 3b49a8d..e503a86 100644 --- a/src/starboard/nplb/file_write_test.cc +++ b/src/starboard/nplb/file_write_test.cc
@@ -25,28 +25,50 @@ namespace nplb { namespace { +// Sets up an empty test fixture, required for typed tests. +template <class SbFileWriteType> +class SbFileWriteTest : public testing::Test {}; + +class SbFileWriter { + public: + static int Write(SbFile file, char* data, int size) { + return SbFileWrite(file, data, size); + } +}; + +class SbFileWriterAll { + public: + static int Write(SbFile file, char* data, int size) { + return SbFileWriteAll(file, data, size); + } +}; + +typedef testing::Types<SbFileWriter, SbFileWriterAll> SbFileWriteTestTypes; + +TYPED_TEST_CASE(SbFileWriteTest, SbFileWriteTestTypes); + const int kBufferLength = 16 * 1024; -TEST(SbFileWriteTest, InvalidFileErrors) { +TYPED_TEST(SbFileWriteTest, InvalidFileErrors) { char buffer[kBufferLength] = {0}; - int result = SbFileWrite(kSbFileInvalid, buffer, kBufferLength); + int result = TypeParam::Write(kSbFileInvalid, buffer, kBufferLength); EXPECT_EQ(-1, result); } -TEST(SbFileWriteTest, BasicWriting) { - // Choose a file size that is not an even multiple of the buffer size, but is - // over several times the size of the buffer. +TYPED_TEST(SbFileWriteTest, BasicWriting) { + // Choose a file size that is not an even multiple of the buffer size, but + // is over several times the size of the buffer. const int kFileSize = kBufferLength * 16 / 3; ScopedRandomFile random_file(0, ScopedRandomFile::kDontCreate); const std::string& filename = random_file.filename(); - SbFile file = - SbFileOpen(filename.c_str(), - kSbFileCreateAlways | kSbFileWrite | kSbFileRead, NULL, NULL); + SbFile file = SbFileOpen(filename.c_str(), + kSbFileCreateAlways | kSbFileWrite | kSbFileRead, + NULL, NULL); ASSERT_TRUE(SbFileIsValid(file)); - // Create a bigger buffer than necessary, so we can test the memory around the - // portion given to SbFileRead. + // Create a bigger buffer than necessary, so we can test the memory around + // the portion given to SbFileRead. char buffer[kBufferLength] = {0}; // Initialize to some arbitrary pattern so we can verify it later. @@ -63,7 +85,7 @@ int remaining = kFileSize - total; int to_write = remaining < kBufferLength ? remaining : kBufferLength; - int bytes_written = SbFileWrite(file, buffer, to_write); + int bytes_written = TypeParam::Write(file, buffer, to_write); // Check that we didn't write more than the buffer size. EXPECT_GE(to_write, bytes_written); @@ -85,7 +107,7 @@ total = 0; int previous_total = 0; while (true) { - int bytes_read = SbFileRead(file, buffer, kBufferLength); + int bytes_read = SbFileReadAll(file, buffer, kBufferLength); if (bytes_read == 0) { break; } @@ -111,7 +133,7 @@ EXPECT_TRUE(result); } -TEST(SbFileWriteTest, WriteZeroBytes) { +TYPED_TEST(SbFileWriteTest, WriteZeroBytes) { ScopedRandomFile random_file(0, ScopedRandomFile::kDontCreate); const std::string& filename = random_file.filename(); @@ -123,7 +145,7 @@ // Write zero bytes. for (int i = 0; i < 10; ++i) { - int bytes_written = SbFileWrite(file, buffer, 0); + int bytes_written = TypeParam::Write(file, buffer, 0); EXPECT_EQ(0, bytes_written); }
diff --git a/src/starboard/nplb/include_all.c b/src/starboard/nplb/include_all.c index 95f0147..e55a271 100644 --- a/src/starboard/nplb/include_all.c +++ b/src/starboard/nplb/include_all.c
@@ -15,26 +15,31 @@ // Includes all headers in a C context to make sure they compile as C files. #include "starboard/atomic.h" +#include "starboard/audio_sink.h" #include "starboard/blitter.h" #include "starboard/byte_swap.h" #include "starboard/character.h" #include "starboard/condition_variable.h" #include "starboard/configuration.h" +#include "starboard/decode_target.h" #include "starboard/directory.h" #include "starboard/double.h" #include "starboard/drm.h" #include "starboard/event.h" #include "starboard/export.h" #include "starboard/file.h" +#include "starboard/input.h" #include "starboard/key.h" #include "starboard/log.h" #include "starboard/media.h" #include "starboard/memory.h" +#include "starboard/microphone.h" #include "starboard/mutex.h" #include "starboard/once.h" #include "starboard/player.h" #include "starboard/socket.h" #include "starboard/socket_waiter.h" +#include "starboard/speech_synthesis.h" #include "starboard/storage.h" #include "starboard/string.h" #include "starboard/system.h"
diff --git a/src/starboard/nplb/include_all_too.c b/src/starboard/nplb/include_all_too.c new file mode 100644 index 0000000..f7d6644 --- /dev/null +++ b/src/starboard/nplb/include_all_too.c
@@ -0,0 +1,19 @@ +// Copyright 2015 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. + +// A second translation unit that includes all the headers to verify there are +// no duplicate symbols. + +#include "starboard/nplb/include_all.c" +
diff --git a/src/starboard/nplb/memory_allocate_aligned_test.cc b/src/starboard/nplb/memory_allocate_aligned_test.cc index 72cb82b..dbdc217 100644 --- a/src/starboard/nplb/memory_allocate_aligned_test.cc +++ b/src/starboard/nplb/memory_allocate_aligned_test.cc
@@ -29,7 +29,7 @@ void* memory = SbMemoryAllocateAligned(align, kSize); ASSERT_NE(static_cast<void*>(NULL), memory); EXPECT_TRUE(SbMemoryIsAligned(memory, align)); - SbMemoryFreeAligned(memory); + SbMemoryDeallocateAligned(memory); } } @@ -43,7 +43,7 @@ if (memory) { EXPECT_TRUE(SbMemoryIsAligned(memory, align)); } - SbMemoryFreeAligned(memory); + SbMemoryDeallocateAligned(memory); } } @@ -60,7 +60,7 @@ EXPECT_EQ(data[i], static_cast<char>(i)); } - SbMemoryFreeAligned(memory); + SbMemoryDeallocateAligned(memory); } } // namespace
diff --git a/src/starboard/nplb/memory_allocate_test.cc b/src/starboard/nplb/memory_allocate_test.cc index 1da3ae6..87e252c 100644 --- a/src/starboard/nplb/memory_allocate_test.cc +++ b/src/starboard/nplb/memory_allocate_test.cc
@@ -24,20 +24,20 @@ TEST(SbMemoryAllocateTest, AllocatesNormally) { void* memory = SbMemoryAllocate(kSize); EXPECT_NE(static_cast<void*>(NULL), memory); - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryAllocateTest, AllocatesZero) { void* memory = SbMemoryAllocate(0); // We can't expect anything here because some implementations may return an // allocated zero-size memory block, and some implementations may return NULL. - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryAllocateTest, AllocatesOne) { void* memory = SbMemoryAllocate(1); EXPECT_NE(static_cast<void*>(NULL), memory); - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryAllocateTest, CanReadWriteToResult) { @@ -52,7 +52,7 @@ EXPECT_EQ(data[i], static_cast<char>(i)); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } } // namespace
diff --git a/src/starboard/nplb/memory_compare_test.cc b/src/starboard/nplb/memory_compare_test.cc index f0d07c1..2057df7 100644 --- a/src/starboard/nplb/memory_compare_test.cc +++ b/src/starboard/nplb/memory_compare_test.cc
@@ -36,8 +36,8 @@ int result = SbMemoryCompare(memory1, memory2, kSize); EXPECT_EQ(0, result); - SbMemoryFree(memory1); - SbMemoryFree(memory2); + SbMemoryDeallocate(memory1); + SbMemoryDeallocate(memory2); } TEST(SbMemoryCompareTest, SunnyDayLessAndMore) { @@ -62,8 +62,8 @@ result = SbMemoryCompare(memory2, memory1, kSize); EXPECT_GT(0, result); - SbMemoryFree(memory1); - SbMemoryFree(memory2); + SbMemoryDeallocate(memory1); + SbMemoryDeallocate(memory2); } } // namespace
diff --git a/src/starboard/nplb/memory_copy_test.cc b/src/starboard/nplb/memory_copy_test.cc index 1142615..f8d3342 100644 --- a/src/starboard/nplb/memory_copy_test.cc +++ b/src/starboard/nplb/memory_copy_test.cc
@@ -41,8 +41,8 @@ EXPECT_EQ(data2[i], static_cast<char>(i)); } - SbMemoryFree(memory1); - SbMemoryFree(memory2); + SbMemoryDeallocate(memory1); + SbMemoryDeallocate(memory2); } TEST(SbMemoryCopyTest, CopiesZeroData) { @@ -65,8 +65,8 @@ EXPECT_EQ(data2[i], static_cast<char>(0)); } - SbMemoryFree(memory1); - SbMemoryFree(memory2); + SbMemoryDeallocate(memory1); + SbMemoryDeallocate(memory2); } } // namespace
diff --git a/src/starboard/nplb/memory_deallocate_aligned_test.cc b/src/starboard/nplb/memory_deallocate_aligned_test.cc new file mode 100644 index 0000000..39cea85 --- /dev/null +++ b/src/starboard/nplb/memory_deallocate_aligned_test.cc
@@ -0,0 +1,38 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/memory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { +namespace { + +const size_t kSize = 1024 * 128; + +TEST(SbMemoryDeallocateAlignedTest, DeallocatesAligned) { + const size_t kMaxAlign = 4096 + 1; + for (size_t align = 2; align < kMaxAlign; align <<= 1) { + void* memory = SbMemoryAllocateAligned(align, kSize); + SbMemoryDeallocateAligned(memory); + } +} + +TEST(SbMemoryDeallocateAlignedTest, FreesNull) { + SbMemoryDeallocateAligned(NULL); +} + +} // namespace +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/memory_deallocate_test.cc b/src/starboard/nplb/memory_deallocate_test.cc new file mode 100644 index 0000000..9bd8739 --- /dev/null +++ b/src/starboard/nplb/memory_deallocate_test.cc
@@ -0,0 +1,36 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/memory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { +namespace { + +const size_t kSize = 1024 * 128; + +TEST(SbMemoryDeallocateTest, FreesNormally) { + void* memory = SbMemoryAllocate(kSize); + EXPECT_NE(static_cast<void*>(NULL), memory); + SbMemoryDeallocate(memory); +} + +TEST(SbMemoryDeallocateTest, FreesNull) { + SbMemoryDeallocate(NULL); +} + +} // namespace +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/memory_free_aligned_test.cc b/src/starboard/nplb/memory_free_aligned_test.cc deleted file mode 100644 index 5fbc8da..0000000 --- a/src/starboard/nplb/memory_free_aligned_test.cc +++ /dev/null
@@ -1,38 +0,0 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/memory.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace starboard { -namespace nplb { -namespace { - -const size_t kSize = 1024 * 128; - -TEST(SbMemoryFreeAlignedTest, FreesAligned) { - const size_t kMaxAlign = 4096 + 1; - for (size_t align = 2; align < kMaxAlign; align <<= 1) { - void* memory = SbMemoryAllocateAligned(align, kSize); - SbMemoryFreeAligned(memory); - } -} - -TEST(SbMemoryFreeAlignedTest, FreesNull) { - SbMemoryFreeAligned(NULL); -} - -} // namespace -} // namespace nplb -} // namespace starboard
diff --git a/src/starboard/nplb/memory_free_test.cc b/src/starboard/nplb/memory_free_test.cc deleted file mode 100644 index 3e25330..0000000 --- a/src/starboard/nplb/memory_free_test.cc +++ /dev/null
@@ -1,36 +0,0 @@ -// Copyright 2015 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/memory.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace starboard { -namespace nplb { -namespace { - -const size_t kSize = 1024 * 128; - -TEST(SbMemoryFreeTest, FreesNormally) { - void* memory = SbMemoryAllocate(kSize); - EXPECT_NE(static_cast<void*>(NULL), memory); - SbMemoryFree(memory); -} - -TEST(SbMemoryFreeTest, FreesNull) { - SbMemoryFree(NULL); -} - -} // namespace -} // namespace nplb -} // namespace starboard
diff --git a/src/starboard/nplb/memory_move_test.cc b/src/starboard/nplb/memory_move_test.cc index 590a51e..c4b0e39 100644 --- a/src/starboard/nplb/memory_move_test.cc +++ b/src/starboard/nplb/memory_move_test.cc
@@ -41,8 +41,8 @@ EXPECT_EQ(data2[i], static_cast<char>(i)); } - SbMemoryFree(memory1); - SbMemoryFree(memory2); + SbMemoryDeallocate(memory1); + SbMemoryDeallocate(memory2); } TEST(SbMemoryMoveTest, MovesZeroData) { @@ -65,8 +65,8 @@ EXPECT_EQ(data2[i], static_cast<char>(i + 1)); } - SbMemoryFree(memory1); - SbMemoryFree(memory2); + SbMemoryDeallocate(memory1); + SbMemoryDeallocate(memory2); } TEST(SbMemoryMoveTest, MovesOverlappingDataForward) { @@ -84,7 +84,7 @@ EXPECT_EQ(data[i + 3], static_cast<char>(i)); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryMoveTest, MovesOverlappingDataBackwards) { @@ -102,7 +102,7 @@ EXPECT_EQ(data[i], static_cast<char>(i + 3)); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } } // namespace
diff --git a/src/starboard/nplb/memory_reallocate_test.cc b/src/starboard/nplb/memory_reallocate_test.cc index 72d0036..7ca252c 100644 --- a/src/starboard/nplb/memory_reallocate_test.cc +++ b/src/starboard/nplb/memory_reallocate_test.cc
@@ -24,20 +24,20 @@ TEST(SbMemoryReallocateTest, AllocatesNormally) { void* memory = SbMemoryReallocate(NULL, kSize); EXPECT_NE(static_cast<void*>(NULL), memory); - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryReallocateTest, AllocatesZero) { void* memory = SbMemoryReallocate(NULL, 0); // We can't expect anything here because some implementations may return an // allocated zero-size memory block, and some implementations may return NULL. - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryReallocateTest, AllocatesOne) { void* memory = SbMemoryReallocate(NULL, 1); EXPECT_NE(static_cast<void*>(NULL), memory); - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryReallocateTest, CanReadWriteToResult) { @@ -52,7 +52,7 @@ EXPECT_EQ(data[i], static_cast<char>(i)); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryReallocateTest, ReallocatesSmaller) { @@ -74,7 +74,7 @@ EXPECT_EQ(data[i], static_cast<char>(i)); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryReallocateTest, ReallocatesBigger) { @@ -104,7 +104,7 @@ EXPECT_EQ(data[i], static_cast<char>(i)); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryReallocateTest, ReallocatestoZero) { @@ -112,7 +112,7 @@ ASSERT_NE(static_cast<void*>(NULL), memory); memory = SbMemoryReallocate(memory, 0); // See allocates zero above. - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemoryReallocateTest, ReallocatestoSameSize) { @@ -134,7 +134,7 @@ EXPECT_EQ(data[i], static_cast<char>(i)); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } } // namespace
diff --git a/src/starboard/nplb/memory_reporter_test.cc b/src/starboard/nplb/memory_reporter_test.cc new file mode 100644 index 0000000..f49e339 --- /dev/null +++ b/src/starboard/nplb/memory_reporter_test.cc
@@ -0,0 +1,496 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/configuration.h" +#include "starboard/memory.h" +#include "starboard/memory_reporter.h" +#include "starboard/mutex.h" +#include "starboard/thread.h" +#include "starboard/time.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { +namespace { + +// This thread local boolean is used to filter allocations and +// 1) Prevent allocations from other threads. +// 2) Selectively disable allocations so that unintended allocs from +// gTest (ASSERT_XXX) don't cause the current test to fail. +void InitMemoryTrackingState_ThreadLocal(); +bool GetMemoryTrackingEnabled_ThreadLocal(); +void SetMemoryTrackingEnabled_ThreadLocal(bool value); + +// Scoped object that temporary turns off MemoryTracking. This is needed +// to avoid allocations from gTest being inserted into the memory tracker. +struct NoMemTracking { + bool prev_val; + NoMemTracking() { + prev_val = GetMemoryTrackingEnabled_ThreadLocal(); + SetMemoryTrackingEnabled_ThreadLocal(false); + } + ~NoMemTracking() { + SetMemoryTrackingEnabled_ThreadLocal(prev_val); + } +}; + +// EXPECT_XXX and ASSERT_XXX allocate memory, a big no-no when +// for unit testing allocations. These overrides disable memory +// tracking for the duration of the EXPECT and ASSERT operations. +#define EXPECT_EQ_NO_TRACKING(A, B) \ +{ NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_EQ(A, B); \ +} + +#define EXPECT_NE_NO_TRACKING(A, B) \ +{ NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_NE(A, B); \ +} + +#define EXPECT_TRUE_NO_TRACKING(A) \ +{ NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_TRUE(A); \ +} + +#define EXPECT_FALSE_NO_TRACKING(A) \ +{ NoMemTracking no_memory_tracking_in_this_scope; \ + EXPECT_FALSE(A); \ +} + +#define ASSERT_EQ_NO_TRACKING(A, B) \ +{ NoMemTracking no_memory_tracking_in_this_scope; \ + ASSERT_EQ(A, B); \ +} + +#define ASSERT_NE_NO_TRACKING(A, B) \ +{ NoMemTracking no_memory_tracking_in_this_scope; \ + ASSERT_NE(A, B); \ +} + +#define ASSERT_TRUE_NO_TRACKING(A) \ +{ NoMemTracking no_memory_tracking_in_this_scope; \ + ASSERT_TRUE(A); \ +} + +#define ASSERT_FALSE_NO_TRACKING(A) \ +{ NoMemTracking no_memory_tracking_in_this_scope; \ + ASSERT_FALSE(A); \ +} + +/////////////////////////////////////////////////////////////////////////////// +// A memory reporter that is used to watch allocations from the system. +class TestMemReporter { + public: + TestMemReporter() { Construct(); } + + SbMemoryReporter* memory_reporter() { + return &memory_reporter_; + } + + // Removes this object from listening to allocations. + void RemoveGlobalHooks() { + SbMemorySetReporter(NULL); + // Sleep to allow other threads time to pass through. + SbThreadSleep(250 * kSbTimeMillisecond); + } + + // Total number allocations outstanding. + int number_allocs() const { return number_allocs_; } + + void Clear() { + starboard::ScopedLock lock(mutex_); + number_allocs_ = 0; + last_allocation_ = NULL; + last_deallocation_ = NULL; + last_mem_map_ = NULL; + last_mem_unmap_ = NULL; + } + + const void* last_allocation() const { + return last_allocation_; + } + const void* last_deallocation() const { + return last_deallocation_; + } + const void* last_mem_map() const { + return last_mem_map_; + } + const void* last_mem_unmap() const { + return last_mem_unmap_; + } + + private: + // Boilerplate to delegate static function callbacks to the class instance. + static void OnMalloc(void* context, const void* memory, size_t size) { + TestMemReporter* t = static_cast<TestMemReporter*>(context); + t->ReportAlloc(memory, size); + } + + static void OnMapMem(void* context, const void* memory, size_t size) { + TestMemReporter* t = static_cast<TestMemReporter*>(context); + t->ReportMapMem(memory, size); + } + + static void OnDealloc(void* context, const void* memory) { + TestMemReporter* t = static_cast<TestMemReporter*>(context); + t->ReportDealloc(memory); + } + + static void SbUnMapMem(void* context, const void* memory, size_t size) { + TestMemReporter* t = static_cast<TestMemReporter*>(context); + t->ReportUnMapMemory(memory, size); + } + + static SbMemoryReporter CreateSbMemoryReporter(TestMemReporter* context) { + SbMemoryReporter cb = { OnMalloc, OnDealloc, + OnMapMem, SbUnMapMem, + context }; + return cb; + } + + void ReportAlloc(const void* memory, size_t /*size*/) { + if (!GetMemoryTrackingEnabled_ThreadLocal()) { + return; + } + starboard::ScopedLock lock(mutex_); + last_allocation_ = memory; + number_allocs_++; + } + + void ReportMapMem(const void* memory, size_t size) { + if (!GetMemoryTrackingEnabled_ThreadLocal()) { + return; + } + starboard::ScopedLock lock(mutex_); + last_mem_map_ = memory; + number_allocs_++; + } + + void ReportDealloc(const void* memory) { + if (!GetMemoryTrackingEnabled_ThreadLocal()) { + return; + } + starboard::ScopedLock lock(mutex_); + last_deallocation_ = memory; + number_allocs_--; + } + + void ReportUnMapMemory(const void* memory, size_t size) { + if (!GetMemoryTrackingEnabled_ThreadLocal()) { + return; + } + starboard::ScopedLock lock(mutex_); + last_mem_unmap_ = memory; + number_allocs_--; + } + + void Construct() { + Clear(); + memory_reporter_ = CreateSbMemoryReporter(this); + } + + SbMemoryReporter memory_reporter_; + starboard::Mutex mutex_; + const void* last_allocation_; + const void* last_deallocation_; + const void* last_mem_map_; + const void* last_mem_unmap_; + int number_allocs_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Needed by all tests that require a memory tracker to be installed. On the +// first test the test memory tracker is installed and then after the last test +// the memory tracker is removed. +class MemoryReportingTest : public ::testing::Test { + public: + TestMemReporter* mem_reporter() { return s_test_alloc_tracker_; } + bool MemoryReportingEnabled() const { + return s_memory_reporter_error_enabled_; + } + + protected: + // Global setup - runs before the first test in the series. + static void SetUpTestCase() { + InitMemoryTrackingState_ThreadLocal(); + // global init test code should run here. + if (s_test_alloc_tracker_ == NULL) { + s_test_alloc_tracker_ = new TestMemReporter; + } + s_memory_reporter_error_enabled_ = + SbMemorySetReporter(s_test_alloc_tracker_->memory_reporter()); + } + + // Global Teardown after last test has run it's course. + static void TearDownTestCase() { + s_test_alloc_tracker_->RemoveGlobalHooks(); + } + + // Per test setup. + virtual void SetUp() { + mem_reporter()->Clear(); + // Allows current thread to capture memory allocations. If this wasn't + // done then background threads spawned by a framework could notify this + // class about allocations and fail the test. + SetMemoryTrackingEnabled_ThreadLocal(true); + } + + // Per test teardown. + virtual void TearDown() { + SetMemoryTrackingEnabled_ThreadLocal(false); + if (mem_reporter()->number_allocs() != 0) { + ADD_FAILURE_AT(__FILE__, __LINE__) << "Memory Leak detected."; + } + mem_reporter()->Clear(); + } + static TestMemReporter* s_test_alloc_tracker_; + static bool s_memory_reporter_error_enabled_; +}; +TestMemReporter* MemoryReportingTest::s_test_alloc_tracker_ = NULL; +bool MemoryReportingTest::s_memory_reporter_error_enabled_ = false; + +/////////////////////////////////////////////////////////////////////////////// +// TESTS. +// There are two sets of tests: POSITIVE and NEGATIVE. +// The positive tests are active when STARBOARD_ALLOWS_MEMORY_TRACKING is +// defined and test that memory tracking is enabled. +// NEGATIVE tests ensure that tracking is disabled when +// STARBOARD_ALLOWS_MEMORY_TRACKING is not defined. +// When adding new tests: +// POSITIVE tests are named normally. +// NEGATIVE tets are named with "No" prefixed to the beginning. +// Example: +// TEST_F(MemoryReportingTest, CapturesAllocDealloc) <--- POSITIVE test. +// TEST_F(MemoryReportingTest, NoCapturesAllocDealloc) <- NEGATIVE test. +// All positive & negative tests are grouped together. +/////////////////////////////////////////////////////////////////////////////// + +#ifdef STARBOARD_ALLOWS_MEMORY_TRACKING +// These are POSITIVE tests, which test the expectation that when the define +// STARBOARD_ALLOWS_MEMORY_TRACKING is active that the memory tracker will +// receive memory allocations notifications. +// +// Tests the assumption that the SbMemoryAllocate and SbMemoryDeallocate +// will report memory allocations. +TEST_F(MemoryReportingTest, CapturesAllocDealloc) { + if (!MemoryReportingEnabled()) { + GTEST_SUCCEED() << "Memory reporting is disabled."; + return; + } + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + void* memory = SbMemoryAllocate(4); + EXPECT_EQ_NO_TRACKING(1, mem_reporter()->number_allocs()); + + EXPECT_EQ_NO_TRACKING(memory, mem_reporter()->last_allocation()); + + SbMemoryDeallocate(memory); + EXPECT_EQ_NO_TRACKING(mem_reporter()->number_allocs(), 0); + + // Should equal the last allocation. + EXPECT_EQ_NO_TRACKING(memory, mem_reporter()->last_deallocation()); +} + +// Tests the assumption that SbMemoryReallocate() will report a +// deallocation and an allocation to the memory tracker. +TEST_F(MemoryReportingTest, CapturesRealloc) { + if (!MemoryReportingEnabled()) { + GTEST_SUCCEED() << "Memory reporting is disabled."; + return; + } + void* prev_memory = SbMemoryAllocate(4); + void* new_memory = SbMemoryReallocate(prev_memory, 8); + + EXPECT_EQ_NO_TRACKING(new_memory, mem_reporter()->last_allocation()); + EXPECT_EQ_NO_TRACKING(prev_memory, mem_reporter()->last_deallocation()); + + EXPECT_EQ_NO_TRACKING(1, mem_reporter()->number_allocs()); + + SbMemoryDeallocate(new_memory); + EXPECT_EQ_NO_TRACKING(mem_reporter()->number_allocs(), 0); +} + +// Tests the assumption that the SbMemoryMap and SbMemoryUnmap +// will report memory allocations. +TEST_F(MemoryReportingTest, CapturesMemMapUnmap) { + if (!MemoryReportingEnabled()) { + GTEST_SUCCEED() << "Memory reporting is disabled."; + return; + } + const int64_t kMemSize = 4096; + const int kFlags = 0; + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + void* mem_chunk = SbMemoryMap(kMemSize, kFlags, "TestMemMap"); + EXPECT_EQ_NO_TRACKING(1, mem_reporter()->number_allocs()); + + // Now unmap the memory and confirm that this memory was reported as free. + EXPECT_EQ_NO_TRACKING(mem_chunk, mem_reporter()->last_mem_map()); + SbMemoryUnmap(mem_chunk, kMemSize); + EXPECT_EQ_NO_TRACKING(mem_chunk, mem_reporter()->last_mem_unmap()); + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); +} + +// Tests the assumption that the operator/delete will report +// memory allocations. +TEST_F(MemoryReportingTest, CapturesOperatorNewDelete) { + if (!MemoryReportingEnabled()) { + GTEST_SUCCEED() << "Memory reporting is disabled."; + return; + } + EXPECT_TRUE_NO_TRACKING(mem_reporter()->number_allocs() == 0); + int* my_int = new int(); + EXPECT_TRUE_NO_TRACKING(mem_reporter()->number_allocs() == 1); + + bool is_last_allocation = + my_int == mem_reporter()->last_allocation(); + + EXPECT_TRUE_NO_TRACKING(is_last_allocation); + + delete my_int; + EXPECT_TRUE_NO_TRACKING(mem_reporter()->number_allocs() == 0); + + // Expect last deallocation to be the expected pointer. + EXPECT_EQ_NO_TRACKING(my_int, mem_reporter()->last_deallocation()); +} + +#else // !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) +// These are NEGATIVE tests, which test the expectation that when the +// STARBOARD_ALLOWS_MEMORY_TRACKING is undefined that the memory tracker does +// not receive memory allocations notifications. + +TEST_F(MemoryReportingTest, NoCapturesAllocDealloc) { + EXPECT_FALSE_NO_TRACKING(MemoryReportingEnabled()); + + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + void* memory = SbMemoryAllocate(4); + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_allocation()); + + SbMemoryDeallocate(memory); + EXPECT_EQ_NO_TRACKING(mem_reporter()->number_allocs(), 0); + EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_deallocation()); +} + +TEST_F(MemoryReportingTest, NoCapturesRealloc) { + EXPECT_FALSE_NO_TRACKING(MemoryReportingEnabled()); + void* prev_memory = SbMemoryAllocate(4); + void* new_memory = SbMemoryReallocate(prev_memory, 8); + + EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_allocation()); + EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_deallocation()); + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + + SbMemoryDeallocate(new_memory); + EXPECT_EQ_NO_TRACKING(mem_reporter()->number_allocs(), 0); +} + +TEST_F(MemoryReportingTest, NoCapturesMemMapUnmap) { + EXPECT_FALSE_NO_TRACKING(MemoryReportingEnabled()); + const int64_t kMemSize = 4096; + const int kFlags = 0; + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + void* mem_chunk = SbMemoryMap(kMemSize, kFlags, "TestMemMap"); + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + + // Now unmap the memory and confirm that this memory was reported as free. + EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_mem_map()); + SbMemoryUnmap(mem_chunk, kMemSize); + EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_mem_unmap()); + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); +} + +TEST_F(MemoryReportingTest, NoCapturesOperatorNewDelete) { + EXPECT_FALSE_NO_TRACKING(MemoryReportingEnabled()); + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + int* my_int = new int(); + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_allocation()); + + delete my_int; + EXPECT_EQ_NO_TRACKING(0, mem_reporter()->number_allocs()); + EXPECT_EQ_NO_TRACKING(NULL, mem_reporter()->last_deallocation()); +} + +#endif // !defined(STARBOARD_ALLOWS_MEMORY_TRACKING) + +/////////////////////////////// Implementation //////////////////////////////// + +// Simple ThreadLocalBool class which allows a default value to be +// true or false. +class ThreadLocalBool { + public: + ThreadLocalBool() { + // NULL is the destructor. + slot_ = SbThreadCreateLocalKey(NULL); + } + + ~ThreadLocalBool() { + SbThreadDestroyLocalKey(slot_); + } + + void SetEnabled(bool value) { + SetEnabledThreadLocal(value); + } + + bool Enabled() const { + return GetEnabledThreadLocal(); + } + + private: + void SetEnabledThreadLocal(bool value) { + void* bool_as_pointer; + if (value) { + bool_as_pointer = reinterpret_cast<void*>(0x1); + } else { + bool_as_pointer = NULL; + } + SbThreadSetLocalValue(slot_, bool_as_pointer); + } + + bool GetEnabledThreadLocal() const { + void* ptr = SbThreadGetLocalValue(slot_); + return ptr != NULL; + } + + mutable SbThreadLocalKey slot_; + bool default_value_; +}; + +void InitMemoryTrackingState_ThreadLocal() { + GetMemoryTrackingEnabled_ThreadLocal(); +} + +static ThreadLocalBool* GetMemoryTrackingState() { + static ThreadLocalBool* thread_local_bool = new ThreadLocalBool(); + return thread_local_bool; +} + +bool GetMemoryTrackingEnabled_ThreadLocal() { + return GetMemoryTrackingState()->Enabled(); +} + +void SetMemoryTrackingEnabled_ThreadLocal(bool value) { + GetMemoryTrackingState()->SetEnabled(value); +} + +// No use for these macros anymore. +#undef EXPECT_EQ_NO_TRACKING +#undef EXPECT_TRUE_NO_TRACKING +#undef EXPECT_FALSE_NO_TRACKING +#undef ASSERT_EQ_NO_TRACKING +#undef ASSERT_TRUE_NO_TRACKING +#undef ASSERT_FALSE_NO_TRACKING + +} // namespace +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/memory_set_test.cc b/src/starboard/nplb/memory_set_test.cc index 0874104..a086170 100644 --- a/src/starboard/nplb/memory_set_test.cc +++ b/src/starboard/nplb/memory_set_test.cc
@@ -34,7 +34,7 @@ ASSERT_EQ('\xCD', data[i]); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemorySetTest, SetsZeroData) { @@ -50,7 +50,7 @@ ASSERT_EQ(static_cast<char>(i), data[i]); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } TEST(SbMemorySetTest, IgnoresExtraData) { @@ -66,7 +66,7 @@ ASSERT_EQ('\xCD', data[i]); } - SbMemoryFree(memory); + SbMemoryDeallocate(memory); } } // namespace
diff --git a/src/starboard/nplb/microphone_close_test.cc b/src/starboard/nplb/microphone_close_test.cc new file mode 100644 index 0000000..662eb0a --- /dev/null +++ b/src/starboard/nplb/microphone_close_test.cc
@@ -0,0 +1,79 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/microphone.h" +#include "starboard/nplb/microphone_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +TEST(SbMicrophoneCloseTest, SunnyDayCloseAreCalledMultipleTimes) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_info_array = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_info_array, 0); + + if (available_info_array != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + bool success = SbMicrophoneOpen(microphone); + EXPECT_TRUE(success); + + success = SbMicrophoneClose(microphone); + EXPECT_TRUE(success); + + success = SbMicrophoneClose(microphone); + EXPECT_TRUE(success); + + success = SbMicrophoneClose(microphone); + EXPECT_TRUE(success); + + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneCloseTest, SunnyDayOpenIsNotCalled) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_info_array = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_info_array, 0); + + if (available_info_array != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + bool success = SbMicrophoneClose(microphone); + EXPECT_TRUE(success); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneCloseTest, RainyDayCloseWithInvalidMicrophone) { + bool success = SbMicrophoneClose(kSbMicrophoneInvalid); + EXPECT_FALSE(success); +} + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/microphone_create_test.cc b/src/starboard/nplb/microphone_create_test.cc new file mode 100644 index 0000000..f89be07 --- /dev/null +++ b/src/starboard/nplb/microphone_create_test.cc
@@ -0,0 +1,182 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/microphone.h" +#include "starboard/nplb/microphone_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +TEST(SbMicrophoneCreateTest, SunnyDayOnlyOneMicrophone) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + EXPECT_TRUE(SbMicrophoneIsValid(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneCreateTest, RainyDayOneMicrophoneIsCreatedMultipleTimes) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + EXPECT_TRUE(SbMicrophoneIsValid(microphone)); + + SbMicrophone microphone_1 = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + EXPECT_FALSE(SbMicrophoneIsValid(microphone_1)); + + SbMicrophone microphone_2 = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + EXPECT_FALSE(SbMicrophoneIsValid(microphone_2)); + + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneCreateTest, RainyDayInvalidMicrophoneId) { + SbMicrophone microphone = SbMicrophoneCreate( + kSbMicrophoneIdInvalid, kNormallyUsedSampleRateInHz, kBufferSize); + EXPECT_FALSE(SbMicrophoneIsValid(microphone)); +} + +TEST(SbMicrophoneCreateTest, RainyDayInvalidFrequency_0) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_FALSE(SbMicrophoneIsSampleRateSupported(info_array[0].id, 0)); + SbMicrophone microphone = + SbMicrophoneCreate(info_array[0].id, 0, kBufferSize); + EXPECT_FALSE(SbMicrophoneIsValid(microphone)); + } +} + +TEST(SbMicrophoneCreateTest, RainyDayInvalidFrequency_Negative) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_FALSE(SbMicrophoneIsSampleRateSupported(info_array[0].id, -8000)); + SbMicrophone microphone = + SbMicrophoneCreate(info_array[0].id, -8000, kBufferSize); + EXPECT_FALSE(SbMicrophoneIsValid(microphone)); + } +} + +TEST(SbMicrophoneCreateTest, RainyDayInvalidFrequency_MoreThanMax) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_FALSE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz + 1)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz + 1, kBufferSize); + EXPECT_FALSE(SbMicrophoneIsValid(microphone)); + } +} + +TEST(SbMicrophoneCreateTest, RainyDayInvalidBufferSize_0) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, 0); + EXPECT_FALSE(SbMicrophoneIsValid(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneCreateTest, SunnyDayBufferSize_1) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, 1); + EXPECT_TRUE(SbMicrophoneIsValid(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneCreateTest, RainyDayInvalidBufferSize_Negative) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, -1024); + EXPECT_FALSE(SbMicrophoneIsValid(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneCreateTest, RainyDayInvalidBufferSize_2G) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = + SbMicrophoneCreate(info_array[0].id, info_array[0].max_sample_rate_hz, + 2 * 1024 * 1024 * 1024L); + EXPECT_FALSE(SbMicrophoneIsValid(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/microphone_destroy_test.cc b/src/starboard/nplb/microphone_destroy_test.cc new file mode 100644 index 0000000..707a3f1 --- /dev/null +++ b/src/starboard/nplb/microphone_destroy_test.cc
@@ -0,0 +1,30 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/microphone.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +TEST(SbMicrophoneDestroyTest, DestroyInvalidMicrophone) { + SbMicrophoneDestroy(kSbMicrophoneInvalid); +} + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/microphone_get_available_test.cc b/src/starboard/nplb/microphone_get_available_test.cc new file mode 100644 index 0000000..232995b --- /dev/null +++ b/src/starboard/nplb/microphone_get_available_test.cc
@@ -0,0 +1,59 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/microphone.h" +#include "starboard/nplb/microphone_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +TEST(SbMicrophoneGetAvailableTest, SunnyDay) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); +} + +TEST(SbMicrophoneGetAvailableTest, RainyDay0NumberOfMicrophone) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) { + int available_microphones = SbMicrophoneGetAvailable(info_array, 0); + EXPECT_GT(available_microphones, 0); + } +} + +TEST(SbMicrophoneGetAvailableTest, RainyDayNegativeNumberOfMicrophone) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) { + int available_microphones = SbMicrophoneGetAvailable(info_array, -10); + EXPECT_GT(available_microphones, 0); + } +} + +TEST(SbMicrophoneGetAvailableTest, RainyDayNULLInfoArray) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + if (SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone) > 0) { + int available_microphones = + SbMicrophoneGetAvailable(NULL, kMaxNumberOfMicrophone); + EXPECT_GT(available_microphones, 0); + } +} + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/microphone_helpers.h b/src/starboard/nplb/microphone_helpers.h new file mode 100644 index 0000000..06d4c37 --- /dev/null +++ b/src/starboard/nplb/microphone_helpers.h
@@ -0,0 +1,32 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_NPLB_MICROPHONE_HELPERS_H_ +#define STARBOARD_NPLB_MICROPHONE_HELPERS_H_ + +#include "starboard/microphone.h" + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +namespace starboard { +namespace nplb { +const int kMaxNumberOfMicrophone = 20; +const int kBufferSize = 32 * 1024; +const int kNormallyUsedSampleRateInHz = 16000; +} // namespace nplb +} // namespace starboard + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +#endif // STARBOARD_NPLB_MICROPHONE_HELPERS_H_
diff --git a/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc b/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc new file mode 100644 index 0000000..1016b8d --- /dev/null +++ b/src/starboard/nplb/microphone_is_sample_rate_supported_test.cc
@@ -0,0 +1,55 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/microphone.h" +#include "starboard/nplb/microphone_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +TEST(SbMicrophoneIsSampleRateSupportedTest, SunnyDay) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + EXPECT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + } +} + +TEST(SbMicrophoneIsSampleRateSupportedTest, RainyDayInvalidSampleRate) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + EXPECT_FALSE(SbMicrophoneIsSampleRateSupported(info_array[0].id, 0)); + } +} + +TEST(SbMicrophoneIsSampleRateSupportedTest, RainyDayInvalidMicrophoneId) { + EXPECT_FALSE(SbMicrophoneIsSampleRateSupported(kSbMicrophoneIdInvalid, + kNormallyUsedSampleRateInHz)); +} + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/microphone_open_test.cc b/src/starboard/nplb/microphone_open_test.cc new file mode 100644 index 0000000..2853457 --- /dev/null +++ b/src/starboard/nplb/microphone_open_test.cc
@@ -0,0 +1,98 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/microphone.h" +#include "starboard/nplb/microphone_helpers.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +TEST(SbMicrophoneOpenTest, SunnyDay) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + bool success = SbMicrophoneOpen(microphone); + EXPECT_TRUE(success); + success = SbMicrophoneClose(microphone); + EXPECT_TRUE(success); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneOpenTest, SunnyDayNoClose) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + bool success = SbMicrophoneOpen(microphone); + EXPECT_TRUE(success); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneOpenTest, SunnyDayMultipleOpenCalls) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + bool success = SbMicrophoneOpen(microphone); + EXPECT_TRUE(success); + + success = SbMicrophoneOpen(microphone); + EXPECT_TRUE(success); + + success = SbMicrophoneOpen(microphone); + EXPECT_TRUE(success); + + success = SbMicrophoneOpen(microphone); + EXPECT_TRUE(success); + + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneOpenTest, RainyDayOpenWithInvalidMicrophone) { + bool success = SbMicrophoneOpen(kSbMicrophoneInvalid); + EXPECT_FALSE(success); +} + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/microphone_read_test.cc b/src/starboard/nplb/microphone_read_test.cc new file mode 100644 index 0000000..7df0260 --- /dev/null +++ b/src/starboard/nplb/microphone_read_test.cc
@@ -0,0 +1,241 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/microphone.h" +#include "starboard/nplb/microphone_helpers.h" +#include "starboard/thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace nplb { + +#if SB_HAS(MICROPHONE) && SB_VERSION(2) + +TEST(SbMicrophoneReadTest, SunnyDay) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + ASSERT_TRUE(SbMicrophoneOpen(microphone)); + + int requested_bytes = info_array[0].min_read_size; + std::vector<uint8_t> audio_data(requested_bytes, 0); + int read_bytes = + SbMicrophoneRead(microphone, &audio_data[0], audio_data.size()); + EXPECT_GE(read_bytes, 0); + + EXPECT_TRUE(SbMicrophoneClose(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneReadTest, SunnyDayReadIsLargerThanMinReadSize) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + ASSERT_TRUE(SbMicrophoneOpen(microphone)); + + int requested_bytes = info_array[0].min_read_size; + std::vector<uint8_t> audio_data(requested_bytes * 2, 0); + int read_bytes = + SbMicrophoneRead(microphone, &audio_data[0], audio_data.size()); + EXPECT_GE(read_bytes, 0); + + EXPECT_TRUE(SbMicrophoneClose(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneReadTest, SunnyDayOpenSleepCloseAndOpenRead) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + EXPECT_TRUE(SbMicrophoneOpen(microphone)); + + SbThreadSleep(50 * kSbTimeMillisecond); + + EXPECT_TRUE(SbMicrophoneClose(microphone)); + EXPECT_TRUE(SbMicrophoneOpen(microphone)); + + int requested_bytes = info_array[0].min_read_size; + std::vector<uint8_t> audio_data(requested_bytes, 0); + int read_bytes = + SbMicrophoneRead(microphone, &audio_data[0], audio_data.size()); + EXPECT_GE(read_bytes, 0); + EXPECT_LT(read_bytes, audio_data.size()); + + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneReadTest, RainyDayAudioBufferIsNULL) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + ASSERT_TRUE(SbMicrophoneOpen(microphone)); + + int read_bytes = SbMicrophoneRead(microphone, NULL, 0); + EXPECT_EQ(read_bytes, 0); + + EXPECT_TRUE(SbMicrophoneClose(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneReadTest, RainyDayAudioBufferSizeIsSmallerThanRequestedSize) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + ASSERT_TRUE(SbMicrophoneOpen(microphone)); + + int read_bytes = SbMicrophoneRead(microphone, NULL, 1024); + EXPECT_LE(read_bytes, 0); + + EXPECT_TRUE(SbMicrophoneClose(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneReadTest, RainyDayAudioBufferSizeIsSmallerThanMinReadSize) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + ASSERT_TRUE(SbMicrophoneOpen(microphone)); + + int requested_bytes = info_array[0].min_read_size; + std::vector<uint8_t> audio_data(requested_bytes / 2, 0); + int read_bytes = + SbMicrophoneRead(microphone, &audio_data[0], audio_data.size()); + EXPECT_GE(read_bytes, 0); + + EXPECT_TRUE(SbMicrophoneClose(microphone)); + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneReadTest, RainyDayOpenIsNotCalled) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + int requested_bytes = info_array[0].min_read_size; + std::vector<uint8_t> audio_data(requested_bytes, 0); + int read_bytes = + SbMicrophoneRead(microphone, &audio_data[0], audio_data.size()); + // An error should have occurred because open was not called. + EXPECT_LT(read_bytes, 0); + + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneReadTest, RainyDayOpenCloseAndRead) { + SbMicrophoneInfo info_array[kMaxNumberOfMicrophone]; + int available_microphones = + SbMicrophoneGetAvailable(info_array, kMaxNumberOfMicrophone); + EXPECT_GE(available_microphones, 0); + + if (available_microphones != 0) { + ASSERT_TRUE(SbMicrophoneIsSampleRateSupported( + info_array[0].id, info_array[0].max_sample_rate_hz)); + SbMicrophone microphone = SbMicrophoneCreate( + info_array[0].id, info_array[0].max_sample_rate_hz, kBufferSize); + ASSERT_TRUE(SbMicrophoneIsValid(microphone)); + + EXPECT_TRUE(SbMicrophoneOpen(microphone)); + EXPECT_TRUE(SbMicrophoneClose(microphone)); + + int requested_bytes = info_array[0].min_read_size; + std::vector<uint8_t> audio_data(requested_bytes, 0); + int read_bytes = + SbMicrophoneRead(microphone, &audio_data[0], audio_data.size()); + // An error should have occurred because the microphone was closed. + EXPECT_LT(read_bytes, 0); + + SbMicrophoneDestroy(microphone); + } +} + +TEST(SbMicrophoneReadTest, RainyDayMicrophoneIsInvalid) { + std::vector<uint8_t> audio_data(1024, 0); + int read_bytes = + SbMicrophoneRead(kSbMicrophoneInvalid, &audio_data[0], audio_data.size()); + EXPECT_LT(read_bytes, 0); +} + +#endif // SB_HAS(MICROPHONE) && SB_VERSION(2) + +} // namespace nplb +} // namespace starboard
diff --git a/src/starboard/nplb/nplb.gyp b/src/starboard/nplb/nplb.gyp index 5b66821..dc39b90 100644 --- a/src/starboard/nplb/nplb.gyp +++ b/src/starboard/nplb/nplb.gyp
@@ -21,6 +21,7 @@ 'target_name': 'nplb', 'type': '<(gtest_target_type)', 'sources': [ + '<(DEPTH)/starboard/common/test_main.cc', 'atomic_test.cc', 'audio_sink_create_test.cc', 'audio_sink_destroy_test.cc', @@ -78,6 +79,8 @@ 'condition_variable_wait_test.cc', 'condition_variable_wait_timed_test.cc', 'configuration_test.cc', + 'decode_target_create_test.cc', + 'decode_target_provider_test.cc', 'directory_can_open_test.cc', 'directory_close_test.cc', 'directory_create_test.cc', @@ -100,26 +103,34 @@ 'file_truncate_test.cc', 'file_write_test.cc', 'include_all.c', + 'include_all_too.c', 'log_flush_test.cc', 'log_format_test.cc', 'log_is_tty_test.cc', 'log_raw_dump_stack_test.cc', 'log_raw_test.cc', 'log_test.cc', - 'main.cc', 'memory_align_to_page_size_test.cc', 'memory_allocate_aligned_test.cc', 'memory_allocate_test.cc', 'memory_compare_test.cc', 'memory_copy_test.cc', 'memory_find_byte_test.cc', - 'memory_free_aligned_test.cc', - 'memory_free_test.cc', + 'memory_deallocate_aligned_test.cc', + 'memory_deallocate_test.cc', 'memory_get_stack_bounds_test.cc', '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', + 'microphone_destroy_test.cc', + 'microphone_get_available_test.cc', + 'microphone_is_sample_rate_supported_test.cc', + 'microphone_open_test.cc', + 'microphone_read_test.cc', 'mutex_acquire_test.cc', 'mutex_acquire_try_test.cc', 'mutex_create_test.cc', @@ -152,6 +163,7 @@ 'socket_waiter_wait_test.cc', 'socket_waiter_wait_timed_test.cc', 'socket_waiter_wake_up_test.cc', + 'speech_synthesis_basic_test.cc', 'storage_close_record_test.cc', 'storage_delete_record_test.cc', 'storage_get_record_size_test.cc',
diff --git a/src/starboard/nplb/once_test.cc b/src/starboard/nplb/once_test.cc index b40817a..a504fc2 100644 --- a/src/starboard/nplb/once_test.cc +++ b/src/starboard/nplb/once_test.cc
@@ -22,6 +22,7 @@ namespace starboard { namespace nplb { +namespace { int s_global_value; @@ -149,5 +150,14 @@ EXPECT_EQ(0, s_global_value); } -} // namespace nplb -} // namespace starboard +SB_ONCE_INITIALIZE_FUNCTION(int, GetIntSingleton); +TEST(SbOnceTest, InitializeOnceMacroFunction) { + int* int_singelton = GetIntSingleton(); + ASSERT_TRUE(int_singelton); + EXPECT_EQ(0, *int_singelton) + << "Singleton Macro does not default initialize."; +} + +} // namespace. +} // namespace nplb. +} // namespace starboard.
diff --git a/src/starboard/nplb/player_create_test.cc b/src/starboard/nplb/player_create_test.cc index d81c51c..dffa92c 100644 --- a/src/starboard/nplb/player_create_test.cc +++ b/src/starboard/nplb/player_create_test.cc
@@ -44,7 +44,12 @@ SbPlayer player = SbPlayerCreate(window, kSbMediaVideoCodecH264, kSbMediaAudioCodecAac, SB_PLAYER_NO_DURATION, kSbDrmSystemInvalid, &audio_header, - NULL, NULL, NULL, NULL); + NULL, NULL, NULL, NULL +#if SB_VERSION(3) + , + NULL +#endif + ); // NOLINT EXPECT_TRUE(SbPlayerIsValid(player)); SbPlayerDestroy(player); SbWindowDestroy(window);
diff --git a/src/starboard/nplb/socket_helpers.h b/src/starboard/nplb/socket_helpers.h index 5b4e5d7..a01c02d 100644 --- a/src/starboard/nplb/socket_helpers.h +++ b/src/starboard/nplb/socket_helpers.h
@@ -23,7 +23,7 @@ namespace starboard { namespace nplb { -const SbTime kSocketTimeout = kSbTimeSecond / 8; +const SbTime kSocketTimeout = kSbTimeSecond / 5; // Creates a plain TCP/IPv4 socket. static inline SbSocket CreateTcpIpv4Socket() {
diff --git a/src/starboard/nplb/socket_send_to_test.cc b/src/starboard/nplb/socket_send_to_test.cc index b7b2f06..9db1d36 100644 --- a/src/starboard/nplb/socket_send_to_test.cc +++ b/src/starboard/nplb/socket_send_to_test.cc
@@ -15,19 +15,78 @@ // SendTo is largely tested with ReceiveFrom, so look there for more invovled // tests. +#include "starboard/memory.h" +#include "starboard/nplb/socket_helpers.h" #include "starboard/socket.h" +#include "starboard/thread.h" +#include "starboard/time.h" #include "testing/gtest/include/gtest/gtest.h" namespace starboard { namespace nplb { namespace { +// Thread entry point to continuously write to a socket that is expected to +// be closed on another thread. +void* SendToServerSocketEntryPoint(void* trio_as_void_ptr) { + ConnectedTrio* trio = static_cast<ConnectedTrio*>(trio_as_void_ptr); + // The contents of this buffer are inconsequential. + const size_t kBufSize = 1024; + char* send_buf = new char[kBufSize]; + SbMemorySet(send_buf, 0, kBufSize); + + // Continue sending to the socket until it fails to send. It's expected that + // SbSocketSendTo will fail when the server socket closes, but the application + // should + // not terminate. + SbTime start = SbTimeGetNow(); + SbTime now = start; + SbTime kTimeout = kSbTimeSecond; + int result = 0; + while (result >= 0 && (now - start < kTimeout)) { + result = SbSocketSendTo(trio->server_socket, send_buf, kBufSize, NULL); + now = SbTimeGetNow(); + } + + delete[] send_buf; + return NULL; +} + TEST(SbSocketSendToTest, RainyDayInvalidSocket) { char buf[16]; int result = SbSocketSendTo(NULL, buf, sizeof(buf), NULL); EXPECT_EQ(-1, result); } +TEST(SbSocketSendToTest, RainyDaySendToClosedSocket) { + ConnectedTrio trio = + CreateAndConnect(GetPortNumberForTests(), kSocketTimeout); + EXPECT_NE(trio.client_socket, kSbSocketInvalid); + EXPECT_NE(trio.server_socket, kSbSocketInvalid); + EXPECT_NE(trio.listen_socket, kSbSocketInvalid); + + // We don't need the listen socket, so close it. + EXPECT_TRUE(SbSocketDestroy(trio.listen_socket)); + + // Start a thread to write to the client socket. + const bool kJoinable = true; + SbThread send_thread = SbThreadCreate( + 0, kSbThreadNoPriority, kSbThreadNoAffinity, kJoinable, "SendToTest", + SendToServerSocketEntryPoint, static_cast<void*>(&trio)); + + // Close the client, which should cause writes to the server socket to fail. + EXPECT_TRUE(SbSocketDestroy(trio.client_socket)); + + // Wait for the thread to exit and check the last socket error. + void* thread_result; + EXPECT_TRUE(SbThreadJoin(send_thread, &thread_result)); + // Check that the server_socket failed, as expected. + EXPECT_EQ(SbSocketGetLastError(trio.server_socket), kSbSocketErrorFailed); + + // Clean up the server socket. + EXPECT_TRUE(SbSocketDestroy(trio.server_socket)); +} + } // namespace } // namespace nplb } // namespace starboard
diff --git a/src/starboard/nplb/socket_waiter_create_test.cc b/src/starboard/nplb/socket_waiter_create_test.cc index b14882a..6190e64 100644 --- a/src/starboard/nplb/socket_waiter_create_test.cc +++ b/src/starboard/nplb/socket_waiter_create_test.cc
@@ -26,7 +26,7 @@ } TEST(SbSocketWaiterCreateTest, ATon) { - const int kATon = 4096; + const int kATon = 256; for (int i = 0; i < kATon; ++i) { SbSocketWaiter waiter = SbSocketWaiterCreate(); EXPECT_TRUE(SbSocketWaiterIsValid(waiter));
diff --git a/src/starboard/nplb/speech_synthesis_basic_test.cc b/src/starboard/nplb/speech_synthesis_basic_test.cc new file mode 100644 index 0000000..4e01d35 --- /dev/null +++ b/src/starboard/nplb/speech_synthesis_basic_test.cc
@@ -0,0 +1,28 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/speech_synthesis.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3) + +TEST(SbSpeechSynthesisBasicTest, Basic) { + // An implementation must at least support US English + EXPECT_TRUE(SbSpeechSynthesisSetLanguage("en-US")); + SbSpeechSynthesisSpeak("Hello"); + SbSpeechSynthesisCancel(); +} + +#endif // SB_HAS(SPEECH_SYNTHESIS) && SB_VERSION(3) +
diff --git a/src/starboard/nplb/storage_helpers.h b/src/starboard/nplb/storage_helpers.h index 56b4fc2..efc422e 100644 --- a/src/starboard/nplb/storage_helpers.h +++ b/src/starboard/nplb/storage_helpers.h
@@ -27,12 +27,12 @@ const int64_t kStorageSize2 = kStorageSize * 2 + kStorageOffset; // Deletes the storage for the current user. -SB_C_INLINE void ClearStorageRecord() { +static SB_C_INLINE void ClearStorageRecord() { SbStorageDeleteRecord(SbUserGetCurrent()); } // Opens the storage record for the current user, validating that it is valid. -SB_C_INLINE SbStorageRecord OpenStorageRecord() { +static SB_C_INLINE SbStorageRecord OpenStorageRecord() { SbStorageRecord record = SbStorageOpenRecord(SbUserGetCurrent()); EXPECT_TRUE(SbStorageIsValidRecord(record)); return record; @@ -40,7 +40,8 @@ // Writes a standard pattern of |size| bytes into the given open storage // |record|. -SB_C_INLINE void WriteStorageRecord(SbStorageRecord record, int64_t size) { +static SB_C_INLINE void WriteStorageRecord(SbStorageRecord record, + int64_t size) { char* data = new char[size]; for (int64_t i = 0; i < size; ++i) { data[i] = static_cast<char>(i + 2 % 0xFF); @@ -52,7 +53,8 @@ // Ensures that the storage record for the current user is initialized with the // standard pattern for exactly |length| bytes. -SB_C_INLINE void InitializeStorageRecord(int64_t length) { + +static SB_C_INLINE void InitializeStorageRecord(int64_t length) { ClearStorageRecord(); SbStorageRecord record = OpenStorageRecord(); WriteStorageRecord(record, length); @@ -62,7 +64,7 @@ // Checks a buffer of |total| size for the expected pattern (written in // WriteStorageRecord) to start at |offset| and continue for |length|, and the // rest of the buffer, before and after, should be set to 0. -SB_C_INLINE void CheckStorageBuffer(char* data, +static SB_C_INLINE void CheckStorageBuffer(char* data, int64_t offset, int64_t length, int64_t total) { @@ -83,7 +85,7 @@ // |offset| and reporting the buffer size as |length|, checks that the number of // read bytes is |expected_length| and then checks the buffer for the expected // pattern written in WriteStorageRecord over the expected range of the buffer. -SB_C_INLINE void ReadAndCheckStorage(SbStorageRecord record, +static SB_C_INLINE void ReadAndCheckStorage(SbStorageRecord record, int64_t offset, int64_t expected_length, int64_t length,
diff --git a/src/starboard/nplb/string_duplicate_test.cc b/src/starboard/nplb/string_duplicate_test.cc index d61e0c2..17b794b 100644 --- a/src/starboard/nplb/string_duplicate_test.cc +++ b/src/starboard/nplb/string_duplicate_test.cc
@@ -26,7 +26,7 @@ EXPECT_NE(kNull, dupe); EXPECT_EQ(0, SbStringCompareNoCase(input, dupe)); EXPECT_EQ(SbStringGetLength(input), SbStringGetLength(dupe)); - SbMemoryFree(dupe); + SbMemoryDeallocate(dupe); } TEST(SbStringDuplicateTest, SunnyDay) {
diff --git a/src/starboard/nplb/system_get_property_test.cc b/src/starboard/nplb/system_get_property_test.cc index de94895..ef4078f 100644 --- a/src/starboard/nplb/system_get_property_test.cc +++ b/src/starboard/nplb/system_get_property_test.cc
@@ -70,6 +70,9 @@ BasicTest(kSbSystemPropertyChipsetModelNumber, false, true, __LINE__); BasicTest(kSbSystemPropertyFirmwareVersion, false, true, __LINE__); BasicTest(kSbSystemPropertyNetworkOperatorName, false, true, __LINE__); +#if SB_VERSION(2) + BasicTest(kSbSystemPropertySpeechApiKey, false, true, __LINE__); +#endif // SB_VERSION(2) if (IsCEDevice(SbSystemGetDeviceType())) { BasicTest(kSbSystemPropertyBrandName, true, true, __LINE__);
diff --git a/src/starboard/nplb/thread_helpers.h b/src/starboard/nplb/thread_helpers.h index 0ebbd03..aa4e32d 100644 --- a/src/starboard/nplb/thread_helpers.h +++ b/src/starboard/nplb/thread_helpers.h
@@ -16,10 +16,14 @@ #define STARBOARD_NPLB_THREAD_HELPERS_H_ #include "starboard/condition_variable.h" +#include "starboard/configuration.h" #include "starboard/mutex.h" +#include "starboard/thread.h" #include "starboard/time.h" #include "starboard/types.h" +#include "testing/gtest/include/gtest/gtest.h" + namespace starboard { namespace nplb { @@ -93,6 +97,53 @@ SbTime delay_after_signal; }; +// AbstractTestThread that is a bare bones class wrapper around Starboard +// thread. Subclasses must override Run(). +class AbstractTestThread { + public: + AbstractTestThread() : thread_(kSbThreadInvalid) {} + virtual ~AbstractTestThread() {} + + // Subclasses should override the Run method. + virtual void Run() = 0; + + // Calls SbThreadCreate() with default parameters. + void Start() { + SbThreadEntryPoint entry_point = ThreadEntryPoint; + + thread_ = SbThreadCreate( + 0, // default stack_size. + kSbThreadNoPriority, // default priority. + kSbThreadNoAffinity, // default affinity. + true, // joinable. + "AbstractTestThread", + entry_point, + this); + + if (kSbThreadInvalid == thread_) { + ADD_FAILURE_AT(__FILE__, __LINE__) << "Invalid thread."; + } + return; + } + + void Join() { + if (!SbThreadJoin(thread_, NULL)) { + ADD_FAILURE_AT(__FILE__, __LINE__) << "Could not join thread."; + } + } + + private: + static void* ThreadEntryPoint(void* ptr) { + AbstractTestThread* this_ptr = static_cast<AbstractTestThread*>(ptr); + this_ptr->Run(); + return NULL; + } + + SbThread thread_; + + SB_DISALLOW_COPY_AND_ASSIGN(AbstractTestThread); +}; + } // namespace nplb } // namespace starboard
diff --git a/src/starboard/nplb/thread_join_test.cc b/src/starboard/nplb/thread_join_test.cc index 42a2b6c..8b93e32 100644 --- a/src/starboard/nplb/thread_join_test.cc +++ b/src/starboard/nplb/thread_join_test.cc
@@ -28,6 +28,45 @@ EXPECT_EQ(NULL, result); } +// Tests the expectation that SbThreadJoin() will block until +// the thread function has been run. +TEST(SbThreadLocalValueTest, ThreadJoinWaitsForFunctionRun) { + // Thread functionality needs to bind to functions. In C++11 we'd use a + // lambda function to tie everything together locally, but this + // function-scoped struct with static function emulates this functionality + // pretty well. + struct LocalStatic { + static void* ThreadEntryPoint(void* input) { + int* value = static_cast<int*>(input); + static const SbTime kSleepTime = 10*kSbTimeMillisecond; // 10 ms. + // Wait to write the value to increase likelyhood of catching + // a race condition. + SbThreadSleep(kSleepTime); + (*value)++; + return NULL; + } + }; + + // Try to increase likelyhood of a race condition by running multiple times. + for (int i = 0; i < 10; ++i) { + int num_times_thread_entry_point_run = 0; + SbThread thread = SbThreadCreate( + 0, // Signals automatic thread stack size. + kSbThreadNoPriority, // Signals default priority. + kSbThreadNoAffinity, // Signals default affinity. + true, // joinable thread. + "TestThread", + LocalStatic::ThreadEntryPoint, + &num_times_thread_entry_point_run); + + ASSERT_NE(kSbThreadInvalid, thread) << "Thread creation not successful"; + ASSERT_TRUE(SbThreadJoin(thread, NULL)); + + ASSERT_EQ(1, num_times_thread_entry_point_run) + << "Expected SbThreadJoin() to be blocked until ThreadFunction runs."; + } +} + } // namespace } // namespace nplb } // namespace starboard
diff --git a/src/starboard/nplb/thread_local_value_test.cc b/src/starboard/nplb/thread_local_value_test.cc index 43d32c4..4083684 100644 --- a/src/starboard/nplb/thread_local_value_test.cc +++ b/src/starboard/nplb/thread_local_value_test.cc
@@ -92,6 +92,73 @@ EXPECT_FALSE(my_value.destroyed); } +// Helper function that ensures that the returned key is not recycled. +SbThreadLocalKey CreateTLSKey_NoRecycle(SbThreadLocalDestructor dtor) { + SbThreadLocalKey key = SbThreadCreateLocalKey(NULL); + EXPECT_EQ(NULL, SbThreadGetLocalValue(key)); + // Some Starboard implementations may recycle the original key, so this test + // ensures that in that case it will be reset to NULL. + SbThreadSetLocalValue(key, reinterpret_cast<void*>(1)); + SbThreadDestroyLocalKey(key); + key = SbThreadCreateLocalKey(DestroyThreadLocalValue); + return key; +} + +// Tests the expectation that thread at-exit destructors don't +// run for ThreadLocal pointers that are set to NULL. +TEST(SbThreadLocalValueTest, NoDestructorsForNullValue) { + static int s_num_destructor_calls = 0; // Must be initialized to 0. + s_num_destructor_calls = 0; // Allows test to be re-run. + + // Thread functionality needs to bind to functions. In C++11 we'd use a + // lambda function to tie everything together locally, but this + // function-scoped struct with static members emulates this functionality + // pretty well. + struct LocalStatic { + // Used as a fake destructor for thread-local-storage objects in this + // test. + static void CountsDestructorCalls(void* /*value*/) { + s_num_destructor_calls++; + } + + // Sets a thread local non-NULL value, and then sets it back to NULL. + static void* ThreadEntryPoint(void* ptr) { + SbThreadLocalKey key = *static_cast<SbThreadLocalKey*>(ptr); + EXPECT_EQ(NULL, SbThreadGetLocalValue(key)); + // Set the value and then NULL it out. We expect that because the final + // value set was NULL, that the destructor attached to the thread's + // at-exit function will not run. + SbThreadSetLocalValue(key, reinterpret_cast<void*>(1)); + SbThreadSetLocalValue(key, NULL); + return NULL; + } + }; + + // Setup the thread key and bind the fake test destructor. + SbThreadLocalKey key = + CreateTLSKey_NoRecycle(LocalStatic::CountsDestructorCalls); + EXPECT_EQ(NULL, SbThreadGetLocalValue(key)); + + // Spawn the thread. + SbThread thread = SbThreadCreate( + 0, // Signals automatic thread stack size. + kSbThreadNoPriority, // Signals default priority. + kSbThreadNoAffinity, // Signals default affinity. + true, // joinable thread. + "TestThread", + LocalStatic::ThreadEntryPoint, + static_cast<void*>(&key)); + + ASSERT_NE(kSbThreadInvalid, thread) << "Thread creation not successful"; + // 2nd param is return value from ThreadEntryPoint, which is always NULL. + ASSERT_TRUE(SbThreadJoin(thread, NULL)); + + // No destructors should have run. + EXPECT_EQ(0, s_num_destructor_calls); + + SbThreadDestroyLocalKey(key); +} + TEST(SbThreadLocalValueTest, SunnyDay) { DoSunnyDayTest(true); } @@ -101,15 +168,9 @@ } TEST(SbThreadLocalValueTest, SunnyDayFreshlyCreatedValuesAreNull) { - SbThreadLocalKey key = SbThreadCreateLocalKey(NULL); + SbThreadLocalKey key = CreateTLSKey_NoRecycle(NULL); // NULL dtor. EXPECT_EQ(NULL, SbThreadGetLocalValue(key)); - // Some Starboard implementations may recycle the original key, so this test - // ensures that in that case it will be reset to NULL. - SbThreadSetLocalValue(key, reinterpret_cast<void*>(1)); - SbThreadDestroyLocalKey(key); - - key = SbThreadCreateLocalKey(NULL); EXPECT_EQ(NULL, SbThreadGetLocalValue(key)); SbThreadDestroyLocalKey(key); }
diff --git a/src/starboard/once.h b/src/starboard/once.h index 03039c9..8eba6cb 100644 --- a/src/starboard/once.h +++ b/src/starboard/once.h
@@ -1,43 +1,75 @@ -// Copyright 2015 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. - -// Onces represent initializations that should only ever happen once per -// process, in a thread safe way. - -#ifndef STARBOARD_ONCE_H_ -#define STARBOARD_ONCE_H_ - -#include "starboard/export.h" -#include "starboard/thread_types.h" -#include "starboard/types.h" - -#ifdef __cplusplus -extern "C" { -#endif - -// Function pointer type for methods that can be called va the SbOnce() system. -typedef void (*SbOnceInitRoutine)(void); - -// If SbOnce() was never called before on with |once_control|, this function -// will run |init_routine| in a thread-safe way and then returns true. If -// SbOnce() was called before, the function returns (true) immediately. -// If |once_control| or |init_routine| are invalid, the function returns false. -SB_EXPORT bool SbOnce(SbOnceControl* once_control, - SbOnceInitRoutine init_routine); - -#ifdef __cplusplus -} -#endif - -#endif // STARBOARD_ONCE_H_ +// Copyright 2015 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. + +// Module Overview: Starboard Once module +// +// Onces represent initializations that should only ever happen once per +// process, in a thread-safe way. + +#ifndef STARBOARD_ONCE_H_ +#define STARBOARD_ONCE_H_ + +#include "starboard/export.h" +#include "starboard/thread_types.h" +#include "starboard/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Function pointer type for methods that can be called via the SbOnce() system. +typedef void (*SbOnceInitRoutine)(void); + +// Thread-safely runs |init_routine| only once. +// - If this |once_control| has not run a function yet, this function runs +// |init_routine| in a thread-safe way and then returns |true|. +// - If SbOnce() was called with |once_control| before, the function returns +// |true| immediately. +// - If |once_control| or |init_routine| is invalid, the function returns +// |false|. +SB_EXPORT bool SbOnce(SbOnceControl* once_control, + SbOnceInitRoutine init_routine); + +#ifdef __cplusplus +// Defines a function that will initialize the indicated type once and return +// it. This initialization is thread safe if the type being created is side +// effect free. +// +// These macros CAN ONLY BE USED IN A CC file, never in a header file. +// +// Example (in cc file): +// SB_ONCE_INITIALIZE_FUNCTION(MyClass, GetOrCreateMyClass) +// .. +// MyClass* instance = GetOrCreateMyClass(); +// MyClass* instance2 = GetOrCreateMyClass(); +// DCHECK_EQ(instance, instance2); +#define SB_ONCE_INITIALIZE_FUNCTION(Type, FunctionName) \ +Type* FunctionName() { \ + static SbOnceControl s_once_flag = SB_ONCE_INITIALIZER; \ + static Type* s_singleton = NULL; \ + struct Local { \ + static void Init() { \ + s_singleton = new Type(); \ + } \ + }; \ + SbOnce(&s_once_flag, Local::Init); \ + return s_singleton; \ +} +#endif // __cplusplus + +#ifdef __cplusplus +} +#endif + +#endif // STARBOARD_ONCE_H_
diff --git a/src/starboard/player.h b/src/starboard/player.h index bc19c20..cbd1ff3 100644 --- a/src/starboard/player.h +++ b/src/starboard/player.h
@@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// An interface for controlling playback of media elementary streams. +// Module Overview: Starboard Player module +// +// Defines an interface for controlling playback of media elementary streams. #ifndef STARBOARD_PLAYER_H_ #define STARBOARD_PLAYER_H_ @@ -21,6 +23,7 @@ #if SB_HAS(PLAYER) +#include "starboard/decode_target.h" #include "starboard/drm.h" #include "starboard/export.h" #include "starboard/media.h" @@ -35,13 +38,12 @@ // An indicator of whether the decoder can accept more samples. typedef enum SbPlayerDecoderState { - // The decoder is ready for and eager to receive more samples. Note that - // once the decoder notifies the user with kSbPlayerDecoderStateNeedsData, - // it cannot enter kSbPlayerDecoderStateBufferFull before SbPlayerWriteSample - // or SbPlayerWriteEndOfStream is called. + // The decoder is asking for one more sample. kSbPlayerDecoderStateNeedsData, // The decoder is not ready for any more samples, so do not send them. + // Note that this enum value has been deprecated and the SbPlayer + // implementation should no longer use this value. kSbPlayerDecoderStateBufferFull, // The player has been destroyed, and will send no more callbacks. @@ -50,7 +52,7 @@ // An indicator of the general playback state. typedef enum SbPlayerState { - // The player has just been initialized. It is expecting an SbPlayerSeek() + // The player has just been initialized. It is expecting an SbPlayerSeek() // call to enter the prerolling state. kSbPlayerStateInitialized, @@ -60,8 +62,9 @@ // Prerolling after a Seek. kSbPlayerStatePrerolling, - // The player is presenting media, and it is either actively playing in - // real-time, or it is paused. + // The player is presenting media, and it is either paused or actively + // playing in real-time. Note that the implementation should use this + // state to signal that the preroll has been finished. kSbPlayerStatePresenting, // The player is presenting media, but it is paused at the end of the stream. @@ -70,8 +73,8 @@ // The player has been destroyed, and will send no more callbacks. kSbPlayerStateDestroyed, - // The player encounters an error. It is expecting an SbPlayerDestroy() call - // to tear down the player. Any call to other functions maybe ignored and + // The player encountered an error. It expects an SbPlayerDestroy() call + // to tear down the player. Calls to other functions may be ignored and // callbacks may not be triggered. kSbPlayerStateError, } SbPlayerState; @@ -124,7 +127,11 @@ // this callback was dispatched. This is to distinguish status callbacks for // interrupting seeks. These callbacks will happen on a different thread than // the calling thread, and it is not valid to call SbPlayer functions from -// within this callback. +// within this callback. After an update with kSbPlayerDecoderStateNeedsData, +// the user of the player will make at most one call to SbPlayerWriteSample() or +// SbPlayerWriteEndOfStream(). The player implementation should update the +// decoder status again after such call to notify its user to continue writing +// more frames. typedef void (*SbPlayerDecoderStatusFunc)(SbPlayer player, void* context, SbMediaType type, @@ -166,7 +173,7 @@ #define kSbPlayerInvalid ((SbPlayer)NULL) // Returns whether the given player handle is valid. -SB_C_INLINE bool SbPlayerIsValid(SbPlayer player) { +static SB_C_INLINE bool SbPlayerIsValid(SbPlayer player) { return player != kSbPlayerInvalid; } @@ -174,108 +181,146 @@ // Creates a player that will be displayed on |window| for the specified // |video_codec| and |audio_codec|, acquiring all resources needed to operate -// it, and returning an opaque handle to it. |window| can be kSbWindowInvalid -// for platforms where video will be only displayed on a particular window -// which the underlying implementation already has access to. If |video_codec| -// is kSbMediaVideoCodecNone, the player is an audio-only player. Otherwise, the -// player is an audio/video decoder. |audio_codec| should never be -// kSbMediaAudioCodecNone. The expectation is that a new player will be created -// and destroyed for every playback. +// it, and returning an opaque handle to it. The expectation is that a new +// player will be created and destroyed for every playback. // -// |duration_pts| is the expected media duration in 90KHz ticks (PTS). It may be -// set to SB_PLAYER_NO_DURATION for live streams. +// This function returns the created player. Note the following: +// - The associated decoder of the returned player should be assumed to not be +// in |kSbPlayerDecoderStateNeedsData| until SbPlayerSeek() has been called +// on it. +// - It is expected either that the thread that calls SbPlayerCreate is the same +// thread that calls the other |SbPlayer| functions for that player, or that +// there is a mutex guarding calls into each |SbPlayer| instance. +// - If there is a platform limitation on how many players can coexist +// simultaneously, then calls made to this function that attempt to exceed +// that limit will return |kSbPlayerInvalid|. // -// If the media stream has encrypted portions, then an appropriate DRM system -// must first be created with SbDrmCreateSystem() and passed into |drm_system|. -// If not, then |drm_system| may be kSbDrmSystemInvalid. +// |window|: The window that will display the player. |window| can be +// |kSbWindowInvalid| for platforms where video is only displayed on a +// particular window that the underlying implementation already has access to. // -// If |audio_codec| is kSbMediaAudioCodecAac, then the caller must provide a -// populated |audio_header|. Otherwise, this may be NULL. +// |video_codec|: The video codec used for the player. If |video_codec| is +// |kSbMediaVideoCodecNone|, the player is an audio-only player. If +// |video_codec| is any other value, the player is an audio/video decoder. // -// If not NULL, the player will call |sample_deallocator_func| on an internal -// thread to free the sample buffers passed into SbPlayerWriteSample(). +// |audio_codec|: The audio codec used for the player. The value should never +// be |kSbMediaAudioCodecNone|. In addition, the caller must provide a +// populated |audio_header| if the audio codec is |kSbMediaAudioCodecAac|. // -// If not NULL, the decoder will call |decoder_status_func| on an internal -// thread to provide an update on the decoder's status. No work should be done -// on this thread, it should just signal the client thread interacting with the -// decoder. +// |duration_pts|: The expected media duration in 90KHz ticks (PTS). It may be +// set to |SB_PLAYER_NO_DURATION| for live streams. // -// If not NULL, the player will call |player_status_func| on an internal thread -// to provide an update on the playback status. No work should be done on this -// thread, it should just signal the client thread interacting with the decoder. +// |drm_system|: If the media stream has encrypted portions, then this +// parameter provides an appropriate DRM system, created with +// |SbDrmCreateSystem()|. If the stream does not have encrypted portions, +// then |drm_system| may be |kSbDrmSystemInvalid|. // -// |context| will be passed back into all callbacks, and is generally used to -// point at a class or struct that contains state associated with the player. +// |audio_header|: Note that the caller must provide a populated |audio_header| +// if the audio codec is |kSbMediaAudioCodecAac|. Otherwise, |audio_header| +// can be NULL. See media.h for the format of the |SbMediaAudioHeader| struct. // -// The associated decoder of the returned player should be assumed to be in -// kSbPlayerDecoderStateBufferFull until SbPlayerSeek() has been called on it. +// |sample_deallocator_func|: If not |NULL|, the player calls this function +// on an internal thread to free the sample buffers passed into +// SbPlayerWriteSample(). // -// It is expected that the thread that calls SbPlayerCreate is the same thread -// that calls the other SbPlayer functions for that player, or that there is a -// mutex guarding calls into each SbPlayer instance. +// |decoder_status_func|: If not |NULL|, the decoder calls this function on an +// internal thread to provide an update on the decoder's status. No work +// should be done on this thread. Rather, it should just signal the client +// thread interacting with the decoder. // -// If there is a platform limitation on how many players can coexist -// simultaneously, then calls made to this function that attempt to exceed that -// limit will return kSbPlayerInvalid. -SB_EXPORT SbPlayer -SbPlayerCreate(SbWindow window, - SbMediaVideoCodec video_codec, - SbMediaAudioCodec audio_codec, - SbMediaTime duration_pts, - SbDrmSystem drm_system, - const SbMediaAudioHeader* audio_header, - SbPlayerDeallocateSampleFunc sample_deallocate_func, - SbPlayerDecoderStatusFunc decoder_status_func, - SbPlayerStatusFunc player_status_func, - void* context); +// |player_status_func|: If not |NULL|, the player calls this function on an +// internal thread to provide an update on the playback status. No work +// should be done on this thread. Rather, it should just signal the client +// thread interacting with the decoder. +// +// |context|: This is passed to all callbacks and is generally used to point +// at a class or struct that contains state associated with the player. +// +// |provider|: Only present in Starboard version 3 and up. If not |NULL|, +// then when SB_HAS(PLAYER_PRODUCING_TEXTURE) is true, the player MAY use the +// provider to create SbDecodeTargets. A provider could also potentially be +// required by the player, in which case, if the provider is not given, the +// player will fail by returning kSbPlayerInvalid. +SB_EXPORT SbPlayer SbPlayerCreate( + SbWindow window, + SbMediaVideoCodec video_codec, + SbMediaAudioCodec audio_codec, + SbMediaTime duration_pts, + SbDrmSystem drm_system, + const SbMediaAudioHeader* audio_header, + SbPlayerDeallocateSampleFunc sample_deallocate_func, + SbPlayerDecoderStatusFunc decoder_status_func, + SbPlayerStatusFunc player_status_func, + void* context +#if SB_VERSION(3) + , + SbDecodeTargetProvider* provider +#endif // SB_VERSION(3) + ); // NOLINT // Destroys |player|, freeing all associated resources. Each callback must -// receive one more callback to say that the player was destroyed. Callbacks +// receive one more callback to say that the player was destroyed. Callbacks // may be in-flight when SbPlayerDestroy is called, and should be ignored once // this function is called. // -// It is not allowed to pass |player| into any other SbPlayer function once -// SbPlayerDestroy has been called on it. +// It is not allowed to pass |player| into any other |SbPlayer| function once +// SbPlayerDestroy has been called on that player. +// +// |player|: The player to be destroyed. SB_EXPORT void SbPlayerDestroy(SbPlayer player); -// Tells the player to freeze playback where it is (if it has already started), -// reset/flush the decoder pipeline, and go back to the Prerolling state. The -// player should restart playback once it can display the frame at -// |seek_to_pts|, or the closest it can get (some players can only seek to -// I-Frames, for example). The client should send no more audio or video samples -// until SbPlayerDecoderStatusFunc is called back with -// kSbPlayerDecoderStateNeedsData, for each required media type. +// Tells the player to freeze playback (if playback has already started), +// reset or flush the decoder pipeline, and go back to the Prerolling state. +// The player should restart playback once it can display the frame at +// |seek_to_pts|, or the closest it can get. (Some players can only seek to +// I-Frames, for example.) // -// A call to seek may interrupt another seek. +// - Seek must be called before samples are sent when starting playback for +// the first time, or the client never receives the +// |kSbPlayerDecoderStateNeedsData| signal. +// - A call to seek may interrupt another seek. +// - After this function is called, the client should not send any more audio +// or video samples until |SbPlayerDecoderStatusFunc| is called back with +// |kSbPlayerDecoderStateNeedsData| for each required media type. +// |SbPlayerDecoderStatusFunc| is the |decoder_status_func| callback function +// that was specified when the player was created (SbPlayerCreate). // -// |ticket| is a user-supplied unique ID that will be passed to all subsequent -// SbPlayerDecoderStatusFunc calls. This is used to filter calls that may have -// been in flight when SbPlayerSeek is called. To be very specific, once -// SbPlayerSeek has been called with ticket X, a client should ignore all -// SbPlayerDecoderStatusFunc calls that don't pass in ticket X. +// |player|: The SbPlayer in which the seek operation is being performed. +// |seek_to_pts|: The frame at which playback should begin. +// |ticket|: A user-supplied unique ID that is be passed to all subsequent +// |SbPlayerDecoderStatusFunc| calls. (That is the |decoder_status_func| +// callback function specified when calling SbPlayerCreate.) // -// Seek must also be called before samples are sent when starting playback for -// the first time, or the client will never receive the -// kSbPlayerDecoderStateNeedsData signal. +// The |ticket| value is used to filter calls that may have been in flight +// when SbPlayerSeek was called. To be very specific, once SbPlayerSeek has +// been called with ticket X, a client should ignore all +// |SbPlayerDecoderStatusFunc| calls that do not pass in ticket X. SB_EXPORT void SbPlayerSeek(SbPlayer player, SbMediaTime seek_to_pts, int ticket); -// Writes a sample of the given media type to |player|'s input stream. -// |sample_type| is the type of sample, audio or video. |sample_buffer| is a -// pointer to a buffer with the data for this sample. This buffer is expected to -// be a portion of a H.264 bytestream, containing a sequence of whole NAL Units -// for video, or a complete audio frame. |sample_buffer_size| is the number of -// bytes in the given sample. |sample_pts| is the timestamp of the sample in -// 90KHz ticks (PTS), and samples MAY be written "slightly" out-of-order. -// |video_sample_info| must be provided for every call where |sample_type| is -// kSbMediaTypeVideo, and must be NULL otherwise. |sample_drm_info| must be -// provided for encrypted samples, and must be NULL otherwise. +// Writes a sample of the given media type to |player|'s input stream. The +// lifetime of |video_sample_info| and |sample_drm_info| (as well as member +// |subsample_mapping| contained inside it) are not guaranteed past the call +// to SbPlayerWriteSample. That means that before returning, the implementation +// must synchronously copy any information it wants to retain from those +// structures. // -// The lifetime of |video_sample_info| and |sample_drm_info| are not guaranteed -// past the call to SbPlayerWriteSample, so the implementation must copy any -// information it wants to retain from those structures synchronously, before it -// returns. +// |player|: The player to which the sample is written. +// |sample_type|: The type of sample being written. See the |SbMediaType| +// enum in media.h. +// |sample_buffer|: A pointer to a buffer with the data for this sample. The +// buffer is expected to be a portion of a bytestream of the codec type that +// the player was created with. The buffer should contain a sequence of whole +// NAL Units for video, or a complete audio frame. +// |sample_buffer_size|: The number of bytes in the given sample. +// |sample_pts|: The timestamp of the sample in 90KHz ticks (PTS). Note that +// samples MAY be written "slightly" out of order. +// |video_sample_info|: Information about a video sample. This value is +// required if |sample_type| is |kSbMediaTypeVideo|. Otherwise, it must be +// |NULL|. +// |sample_drm_info|: The DRM system for the media sample. This value is +// required for encrypted samples. Otherwise, it must be |NULL|. SB_EXPORT void SbPlayerWriteSample( SbPlayer player, SbMediaType sample_type, @@ -285,19 +330,32 @@ const SbMediaVideoSampleInfo* video_sample_info, const SbDrmSampleInfo* sample_drm_info); -// Writes a marker to |player|'s input stream of |stream_type| that there are no -// more samples for that media type for the remainder of this media stream. -// This marker is invalidated, along with the rest of the contents of the -// stream, after a call to SbPlayerSeek. +// Writes a marker to |player|'s input stream of |stream_type| indicating that +// there are no more samples for that media type for the remainder of this +// media stream. This marker is invalidated, along with the rest of the stream's +// contents, after a call to SbPlayerSeek. +// +// |player|: The player to which the marker is written. +// |stream_type|: The type of stream for which the marker is written. SB_EXPORT void SbPlayerWriteEndOfStream(SbPlayer player, SbMediaType stream_type); #if SB_IS(PLAYER_PUNCHED_OUT) -// Sets the player bounds to the given graphics plane coordinates. Will not take -// effect until the next graphics frame buffer swap. The default bounds for a -// player are the full screen. This function should be expected to be called up -// to once per frame, so implementors should take care to avoid related -// performance concerns with such frequent calls. +// Sets the player bounds to the given graphics plane coordinates. The changes +// do not take effect until the next graphics frame buffer swap. The default +// bounds for a player is the full screen. +// +// This function is called on every graphics frame that changes the video +// bounds. For example, if the video bounds are being animated, then this will +// be called at up to 60 Hz. Since the function could be called up to once per +// frame, implementors should take care to avoid related performance concerns +// with such frequent calls. +// +// |player|: The player that is being resized. +// |x|: The x-coordinate of the upper-left corner of the player. +// |y|: The y-coordinate of the upper-left corner of the player. +// |width|: The width of the player, in pixels. +// |height|: The height of the player, in pixels. SB_EXPORT void SbPlayerSetBounds(SbPlayer player, int x, int y, @@ -305,30 +363,42 @@ int height); #endif -// Sets the pause status for |player|. If |player| is in kPlayerStatePrerolling, -// this will set the initial pause state for the current seek target. +// Pauses or unpauses the |player|. If the |player|'s state is +// |kPlayerStatePrerolling|, this function sets the initial pause state for +// the current seek target. SB_EXPORT void SbPlayerSetPause(SbPlayer player, bool pause); -// Sets the volume for |player|, in [0.0, 1.0]. 0.0 means the audio should be -// muted, and 1.0 means it should be played at full volume. +// Sets the player's volume. +// +// |player|: The player in which the volume is being adjusted. +// |volume|: The new player volume. The value must be between |0.0| and |1.0|, +// inclusive. A value of |0.0| means that the audio should be muted, and a +// value of |1.0| means that it should be played at full volume. SB_EXPORT void SbPlayerSetVolume(SbPlayer player, double volume); -// Gets a snapshot of the current player state and writes it into -// |out_player_info|. This function is expected to be inexpensive, and may be -// called very frequently. +// Gets a snapshot of the current player state and writes it to +// |out_player_info|. This function may be called very frequently and is +// expected to be inexpensive. +// +// |player|: The player about which information is being retrieved. +// |out_player_info|: The information retrieved for the player. SB_EXPORT void SbPlayerGetInfo(SbPlayer player, SbPlayerInfo* out_player_info); #if SB_IS(PLAYER_COMPOSITED) // Gets a handle that represents the player's video output, for the purpose of -// composing with SbCompositor (currently undefined). +// composing with |SbCompositor|, which is currently undefined. +// +// |player|: The player for which the video output handle is retrieved. SB_EXPORT SbPlayerCompositionHandle SbPlayerGetCompositionHandle(SbPlayer player); #endif #if SB_IS(PLAYER_PRODUCING_TEXTURE) // Gets an OpenGL texture ID that points to the player's video output frame at -// the time it was called. This can be called once, and the same texture ID will -// be appropriately mapped to the current video frame when drawn. +// the time it was called. This function can be called once, and the texture ID +// will be appropriately remapped to the current video frame when it is drawn. +// +// |player|: The player for which the texture ID is retrieved. SB_EXPORT uint32_t SbPlayerGetTextureId(SbPlayer player); #endif
diff --git a/src/starboard/queue.h b/src/starboard/queue.h index 65127f0..53f7633 100644 --- a/src/starboard/queue.h +++ b/src/starboard/queue.h
@@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -// A C++-only synchronized queue implementation, built entirely on top of -// Starboard synchronization primitives. Can be safely used by both clients and -// implementations. +// Module Overview: Starboard Queue module +// +// Defines a C++-only synchronized queue implementation, built entirely on top +// of Starboard synchronization primitives. It can be safely used by both +// clients and implementations. #ifndef STARBOARD_QUEUE_H_ #define STARBOARD_QUEUE_H_
diff --git a/src/starboard/raspi/1/gyp_configuration.gypi b/src/starboard/raspi/1/gyp_configuration.gypi index 12a59f3..040ca1e 100644 --- a/src/starboard/raspi/1/gyp_configuration.gypi +++ b/src/starboard/raspi/1/gyp_configuration.gypi
@@ -17,7 +17,6 @@ 'target_arch': 'arm', 'target_os': 'linux', - 'enable_webdriver': '1', 'in_app_dial%': 0, 'sysroot%': '/', 'gl_type': 'system_gles2', @@ -32,7 +31,9 @@ # This should have a default value in cobalt/base.gypi. See the comment # there for acceptable values for this variable. - 'javascript_engine': 'javascriptcore', + 'javascript_engine': 'mozjs', + 'cobalt_enable_jit': 0, + 'cobalt_minimum_frame_time_in_milliseconds': '33', # RasPi 1 is ARMv6 'arm_version': 6,
diff --git a/src/starboard/raspi/1/starboard_platform.gyp b/src/starboard/raspi/1/starboard_platform.gyp index 6d8c257..c39d4e3 100644 --- a/src/starboard/raspi/1/starboard_platform.gyp +++ b/src/starboard/raspi/1/starboard_platform.gyp
@@ -33,7 +33,17 @@ '<(DEPTH)/starboard/linux/shared/system_has_capability.cc', '<(DEPTH)/starboard/raspi/1/system_get_property.cc', '<(DEPTH)/starboard/raspi/shared/application_dispmanx.cc', + '<(DEPTH)/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc', + '<(DEPTH)/starboard/raspi/shared/dispmanx_util.cc', + '<(DEPTH)/starboard/raspi/shared/dispmanx_util.h', '<(DEPTH)/starboard/raspi/shared/main.cc', + '<(DEPTH)/starboard/raspi/shared/open_max/open_max_component.cc', + '<(DEPTH)/starboard/raspi/shared/open_max/open_max_component.h', + '<(DEPTH)/starboard/raspi/shared/open_max/open_max_video_decode_component.cc', + '<(DEPTH)/starboard/raspi/shared/open_max/open_max_video_decode_component.h', + '<(DEPTH)/starboard/raspi/shared/open_max/video_decoder.cc', + '<(DEPTH)/starboard/raspi/shared/open_max/video_decoder.h', + '<(DEPTH)/starboard/raspi/shared/thread_create_priority.cc', '<(DEPTH)/starboard/raspi/shared/window_create.cc', '<(DEPTH)/starboard/raspi/shared/window_destroy.cc', '<(DEPTH)/starboard/raspi/shared/window_get_platform_handle.cc', @@ -46,7 +56,6 @@ '<(DEPTH)/starboard/shared/alsa/audio_sink_get_max_channels.cc', '<(DEPTH)/starboard/shared/alsa/audio_sink_get_nearest_supported_sample_frequency.cc', '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_frame_storage_type_supported.cc', - '<(DEPTH)/starboard/shared/alsa/audio_sink_is_audio_sample_type_supported.cc', '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_aligned_unchecked.cc', '<(DEPTH)/starboard/shared/dlmalloc/memory_allocate_unchecked.cc', '<(DEPTH)/starboard/shared/dlmalloc/memory_free.cc', @@ -58,8 +67,6 @@ '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h', '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.cc', '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_common.h', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc', - '<(DEPTH)/starboard/shared/ffmpeg/ffmpeg_video_decoder.h', '<(DEPTH)/starboard/shared/gcc/atomic_gcc.h', '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc', '<(DEPTH)/starboard/shared/iso/character_is_digit.cc', @@ -179,6 +186,7 @@ '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc', '<(DEPTH)/starboard/shared/posix/thread_sleep.cc', '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc', + '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc', '<(DEPTH)/starboard/shared/posix/time_get_now.cc', '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc', '<(DEPTH)/starboard/shared/posix/time_zone_get_dst_name.cc', @@ -197,6 +205,7 @@ '<(DEPTH)/starboard/shared/pthread/once.cc', '<(DEPTH)/starboard/shared/pthread/thread_create.cc', '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc', + '<(DEPTH)/starboard/shared/pthread/thread_create_priority.h', '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc', '<(DEPTH)/starboard/shared/pthread/thread_detach.cc', '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc', @@ -233,12 +242,17 @@ '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc', '<(DEPTH)/starboard/shared/starboard/media/media_is_output_protected.cc', '<(DEPTH)/starboard/shared/starboard/media/media_set_output_protection.cc', - '<(DEPTH)/starboard/shared/starboard/media/mime_parser.cc', - '<(DEPTH)/starboard/shared/starboard/media/mime_parser.h', + '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc', + '<(DEPTH)/starboard/shared/starboard/media/mime_type.h', '<(DEPTH)/starboard/shared/starboard/new.cc', - '<(DEPTH)/starboard/shared/starboard/player/audio_decoder_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/audio_renderer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_decoder_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/audio_renderer_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_decoder_internal.h', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.cc', + '<(DEPTH)/starboard/shared/starboard/player/filter/video_renderer_internal.h', '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc', '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h', '<(DEPTH)/starboard/shared/starboard/player/player_create.cc', @@ -254,11 +268,8 @@ '<(DEPTH)/starboard/shared/starboard/player/player_worker.h', '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc', '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc', - '<(DEPTH)/starboard/shared/starboard/player/video_decoder_internal.h', '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.cc', '<(DEPTH)/starboard/shared/starboard/player/video_frame_internal.h', - '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.cc', - '<(DEPTH)/starboard/shared/starboard/player/video_renderer_internal.h', '<(DEPTH)/starboard/shared/starboard/queue_application.cc', '<(DEPTH)/starboard/shared/starboard/string_concat.cc', '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc', @@ -268,12 +279,19 @@ '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc', '<(DEPTH)/starboard/shared/starboard/system_request_stop.cc', '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_create_egl.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_destroy.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_get_format.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_get_plane_egl.cc', + '<(DEPTH)/starboard/shared/stub/decode_target_is_opaque.cc', '<(DEPTH)/starboard/shared/stub/drm_close_session.cc', '<(DEPTH)/starboard/shared/stub/drm_create_system.cc', '<(DEPTH)/starboard/shared/stub/drm_destroy_system.cc', '<(DEPTH)/starboard/shared/stub/drm_generate_session_update_request.cc', '<(DEPTH)/starboard/shared/stub/drm_system_internal.h', '<(DEPTH)/starboard/shared/stub/drm_update_session.cc', + '<(DEPTH)/starboard/shared/stub/image_decode.cc', + '<(DEPTH)/starboard/shared/stub/image_is_decode_supported.cc', '<(DEPTH)/starboard/shared/stub/media_is_supported.cc', '<(DEPTH)/starboard/shared/stub/system_clear_platform_error.cc', '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc', @@ -287,6 +305,7 @@ 'STARBOARD_IMPLEMENTATION', ], 'dependencies': [ + '<(DEPTH)/starboard/common/common.gyp:common', '<(DEPTH)/third_party/dlmalloc/dlmalloc.gyp:dlmalloc', '<(DEPTH)/third_party/libevent/libevent.gyp:libevent', 'starboard_base_symbolize',
diff --git a/src/starboard/raspi/1/system_get_property.cc b/src/starboard/raspi/1/system_get_property.cc index 6b5cee9..50f9d92 100644 --- a/src/starboard/raspi/1/system_get_property.cc +++ b/src/starboard/raspi/1/system_get_property.cc
@@ -47,6 +47,7 @@ case kSbSystemPropertyModelName: case kSbSystemPropertyModelYear: case kSbSystemPropertyNetworkOperatorName: + case kSbSystemPropertySpeechApiKey: return false; case kSbSystemPropertyFriendlyName:
diff --git a/src/starboard/raspi/directfb/README.md b/src/starboard/raspi/directfb/README.md index 4d9d9be..a4b4ec7 100644 --- a/src/starboard/raspi/directfb/README.md +++ b/src/starboard/raspi/directfb/README.md
@@ -31,7 +31,7 @@ ## Modifications to /etc/directfbrc -Open or create 'etc/directfbrc' (as root) and add the lines: +Open or create '/etc/directfbrc' (as root) and add the lines: graphics-vt hardware
diff --git a/src/starboard/raspi/shared/application_dispmanx.cc b/src/starboard/raspi/shared/application_dispmanx.cc index ae3fb8e..4f9dddf 100644 --- a/src/starboard/raspi/shared/application_dispmanx.cc +++ b/src/starboard/raspi/shared/application_dispmanx.cc
@@ -36,6 +36,10 @@ using ::starboard::shared::dev_input::DevInput; +namespace { +const int kVideoLayer = -1; +} // namespace + SbWindow ApplicationDispmanx::CreateWindow(const SbWindowOptions* options) { if (SbWindowIsValid(window_)) { return kSbWindowInvalid; @@ -44,8 +48,11 @@ InitializeDispmanx(); SB_DCHECK(IsDispmanxInitialized()); - window_ = new SbWindowPrivate(display_, options); + window_ = new SbWindowPrivate(*display_, options); input_ = DevInput::Create(window_); + + video_renderer_.reset(new DispmanxVideoRenderer(*display_, kVideoLayer)); + return window_; } @@ -76,6 +83,15 @@ SbAudioSinkPrivate::TearDown(); } +void ApplicationDispmanx::AcceptFrame(SbPlayer player, + const scoped_refptr<VideoFrame>& frame, + int x, + int y, + int width, + int height) { + video_renderer_->Update(frame); +} + bool ApplicationDispmanx::MayHaveSystemEvents() { return input_ != NULL; } @@ -102,8 +118,7 @@ return; } - bcm_host_init(); - display_ = vc_dispmanx_display_open(0); + display_.reset(new DispmanxDisplay); SB_DCHECK(IsDispmanxInitialized()); } @@ -113,10 +128,8 @@ } SB_DCHECK(!SbWindowIsValid(window_)); - int result = vc_dispmanx_display_close(display_); - SB_DCHECK(result == 0); - display_ = DISPMANX_NO_HANDLE; - bcm_host_deinit(); + + display_.reset(); } } // namespace shared
diff --git a/src/starboard/raspi/shared/application_dispmanx.h b/src/starboard/raspi/shared/application_dispmanx.h index 6f6a938..d08c406 100644 --- a/src/starboard/raspi/shared/application_dispmanx.h +++ b/src/starboard/raspi/shared/application_dispmanx.h
@@ -15,11 +15,9 @@ #ifndef STARBOARD_RASPI_SHARED_APPLICATION_DISPMANX_H_ #define STARBOARD_RASPI_SHARED_APPLICATION_DISPMANX_H_ -#include <bcm_host.h> - -#include <vector> - +#include "starboard/common/scoped_ptr.h" #include "starboard/configuration.h" +#include "starboard/raspi/shared/dispmanx_util.h" #include "starboard/shared/internal_only.h" #include "starboard/shared/linux/dev_input/dev_input.h" #include "starboard/shared/starboard/application.h" @@ -35,8 +33,7 @@ class ApplicationDispmanx : public ::starboard::shared::starboard::QueueApplication { public: - ApplicationDispmanx() - : display_(DISPMANX_NO_HANDLE), window_(kSbWindowInvalid), input_(NULL) {} + ApplicationDispmanx() : window_(kSbWindowInvalid), input_(NULL) {} ~ApplicationDispmanx() SB_OVERRIDE {} static ApplicationDispmanx* Get() { @@ -51,6 +48,12 @@ // --- Application overrides --- void Initialize() SB_OVERRIDE; void Teardown() SB_OVERRIDE; + void AcceptFrame(SbPlayer player, + const scoped_refptr<VideoFrame>& frame, + int x, + int y, + int width, + int height) SB_OVERRIDE; // --- QueueApplication overrides --- bool MayHaveSystemEvents() SB_OVERRIDE; @@ -60,7 +63,7 @@ private: // Returns whether DISPMANX has been initialized. - bool IsDispmanxInitialized() { return display_ != DISPMANX_NO_HANDLE; } + bool IsDispmanxInitialized() { return display_ != NULL; } // Ensures that X is up, display is populated and connected. void InitializeDispmanx(); @@ -69,7 +72,10 @@ void ShutdownDispmanx(); // The DISPMANX display. - DISPMANX_DISPLAY_HANDLE_T display_; + scoped_ptr<DispmanxDisplay> display_; + + // DISPMANX helper to render video frames. + scoped_ptr<DispmanxVideoRenderer> video_renderer_; // The single open window, if any. SbWindow window_;
diff --git a/src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc b/src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc new file mode 100644 index 0000000..f5026b8 --- /dev/null +++ b/src/starboard/raspi/shared/audio_sink_is_audio_sample_type_supported.cc
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/audio_sink.h" + +bool SbAudioSinkIsAudioSampleTypeSupported( + SbMediaAudioSampleType audio_sample_type) { + return audio_sample_type == kSbMediaAudioSampleTypeInt16; +}
diff --git a/src/starboard/raspi/shared/configuration_public.h b/src/starboard/raspi/shared/configuration_public.h index fcdaf1e..ba25c7f 100644 --- a/src/starboard/raspi/shared/configuration_public.h +++ b/src/starboard/raspi/shared/configuration_public.h
@@ -18,7 +18,7 @@ #define STARBOARD_RASPI_SHARED_CONFIGURATION_PUBLIC_H_ // The API version implemented by this platform. -#define SB_API_VERSION 1 +#define SB_API_VERSION 3 // --- System Header Configuration ------------------------------------------- @@ -46,6 +46,15 @@ // Whether the current platform provides the standard header limits.h. #define SB_HAS_LIMITS_H 1 +// Whether the current platform provides the standard header float.h. +#define SB_HAS_FLOAT_H 1 + +// Whether the current platform has microphone supported. +#define SB_HAS_MICROPHONE 0 + +// Whether the current platform has speech synthesis. +#define SB_HAS_SPEECH_SYNTHESIS 0 + // Type detection for wchar_t. #if defined(__WCHAR_MAX__) && \ (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff) @@ -63,6 +72,14 @@ #define SB_IS_WCHAR_T_SIGNED 1 #endif +// --- Architecture Configuration -------------------------------------------- + +// On the current version of Raspbian, real time thread scheduling seems to be +// broken in that higher priority threads do not always have priority over lower +// priority threads. It looks like the thread created last will always have the +// highest priority. +#define SB_HAS_THREAD_PRIORITY_SUPPORT 1 + // --- Attribute Configuration ----------------------------------------------- // The platform's annotation for forcing a C function to be inlined. @@ -360,6 +377,12 @@ // The maximum number of users that can be signed in at the same time. #define SB_USER_MAX_SIGNED_IN 1 +// --- Timing API ------------------------------------------------------------ + +// Whether this platform has an API to retrieve how long the current thread +// has spent in the executing state. +#define SB_HAS_TIME_THREAD_NOW 1 + // --- Platform Specific Audits ---------------------------------------------- #if !defined(__GNUC__)
diff --git a/src/starboard/raspi/shared/dispmanx_util.cc b/src/starboard/raspi/shared/dispmanx_util.cc new file mode 100644 index 0000000..f33a441 --- /dev/null +++ b/src/starboard/raspi/shared/dispmanx_util.cc
@@ -0,0 +1,169 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/raspi/shared/dispmanx_util.h" + +#include "starboard/common/scoped_ptr.h" +#include "starboard/memory.h" + +namespace starboard { +namespace raspi { +namespace shared { + +namespace { + +const int kElementChangeAttributesFlagSrcRect = 1 << 3; + +class DispmanxAutoUpdate { + public: + DispmanxAutoUpdate() { + handle_ = vc_dispmanx_update_start(0 /*screen*/); + SB_DCHECK(handle_ != DISPMANX_NO_HANDLE); + } + ~DispmanxAutoUpdate() { + if (handle_ != DISPMANX_NO_HANDLE) { + Update(); + } + } + + DISPMANX_UPDATE_HANDLE_T handle() const { return handle_; } + + void Update() { + SB_DCHECK(handle_ != DISPMANX_NO_HANDLE); + int32_t result = vc_dispmanx_update_submit_sync(handle_); + SB_DCHECK(result == 0) << " result=" << result; + handle_ = DISPMANX_NO_HANDLE; + } + + private: + DISPMANX_UPDATE_HANDLE_T handle_; + + SB_DISALLOW_COPY_AND_ASSIGN(DispmanxAutoUpdate); +}; + +} // namespace + +DispmanxResource::DispmanxResource(VC_IMAGE_TYPE_T image_type, + uint32_t width, + uint32_t height, + uint32_t visible_width, + uint32_t visible_height) + : width_(width), + height_(height), + visible_width_(visible_width), + visible_height_(visible_height) { + static const uint32_t kMaxDimension = 1 << 16; + + SB_DCHECK(width_ > 0 && width_ < kMaxDimension); + SB_DCHECK(height_ > 0 && height_ < kMaxDimension); + SB_DCHECK(visible_width_ > 0 && visible_width_ < kMaxDimension); + SB_DCHECK(visible_height > 0 && visible_height < kMaxDimension); + SB_DCHECK(width_ >= visible_width_); + SB_DCHECK(height_ >= visible_height); + + uint32_t vc_image_ptr; + + handle_ = vc_dispmanx_resource_create( + image_type, visible_width_ | (width_ << 16), + visible_height | (height_ << 16), &vc_image_ptr); + SB_DCHECK(handle_ != DISPMANX_NO_HANDLE); +} + +void DispmanxYUV420Resource::WriteData(const void* data) { + SB_DCHECK(handle() != DISPMANX_NO_HANDLE); + + DispmanxRect dst_rect(0, 0, width(), height() * 3 / 2); + int32_t result = vc_dispmanx_resource_write_data( + handle(), VC_IMAGE_YUV420, width(), const_cast<void*>(data), &dst_rect); + SB_DCHECK(result == 0); +} + +void DispmanxYUV420Resource::ClearWithBlack() { + scoped_array<uint8_t> data(new uint8_t[width() * height() * 3 / 2]); + SbMemorySet(data.get(), width() * height(), 0); + SbMemorySet(data.get() + width() * height(), width() * height() / 2, 0x80); + WriteData(data.get()); +} + +void DispmanxRGB565Resource::WriteData(const void* data) { + SB_DCHECK(handle() != DISPMANX_NO_HANDLE); + + DispmanxRect dst_rect(0, 0, width(), height()); + int32_t result = + vc_dispmanx_resource_write_data(handle(), VC_IMAGE_RGB565, width() * 2, + const_cast<void*>(data), &dst_rect); + SB_DCHECK(result == 0); +} + +void DispmanxRGB565Resource::ClearWithBlack() { + scoped_array<uint8_t> data(new uint8_t[width() * height() * 2]); + SbMemorySet(data.get(), width() * height() * 2, 0); + WriteData(data.get()); +} + +DispmanxElement::DispmanxElement(const DispmanxDisplay& display, + int32_t layer, + const DispmanxRect& dest_rect, + const DispmanxResource& src, + const DispmanxRect& src_rect) { + DispmanxAutoUpdate update; + handle_ = vc_dispmanx_element_add(update.handle(), display.handle(), layer, + &dest_rect, src.handle(), &src_rect, + DISPMANX_PROTECTION_NONE, NULL /*alpha*/, + NULL /*clamp*/, DISPMANX_NO_ROTATE); + SB_DCHECK(handle_ != DISPMANX_NO_HANDLE); +} + +DispmanxElement::~DispmanxElement() { + DispmanxAutoUpdate update; + int32_t result = vc_dispmanx_element_remove(update.handle(), handle_); + SB_DCHECK(result == 0) << " result=" << result; +} + +void DispmanxElement::ChangeSource(const DispmanxResource& new_src) { + DispmanxAutoUpdate update; + vc_dispmanx_element_change_source(update.handle(), handle_, new_src.handle()); +} + +DispmanxVideoRenderer::DispmanxVideoRenderer(const DispmanxDisplay& display, + int32_t layer) + : black_frame_(256, 256, 256, 256) { + black_frame_.ClearWithBlack(); + element_.reset(new DispmanxElement(display, layer, DispmanxRect(), + black_frame_, DispmanxRect())); +} + +void DispmanxVideoRenderer::Update( + const scoped_refptr<VideoFrame>& video_frame) { + SB_DCHECK(video_frame); + + if (frame_ == video_frame) { + return; + } + + if (video_frame->IsEndOfStream()) { + element_->ChangeSource(black_frame_); + frame_ = video_frame; + return; + } + + DispmanxYUV420Resource* resource = + reinterpret_cast<DispmanxYUV420Resource*>(video_frame->native_texture()); + element_->ChangeSource(*resource); + frame_ = video_frame; +} + +} // namespace shared +} // namespace raspi +} // namespace starboard
diff --git a/src/starboard/raspi/shared/dispmanx_util.h b/src/starboard/raspi/shared/dispmanx_util.h new file mode 100644 index 0000000..f051e86 --- /dev/null +++ b/src/starboard/raspi/shared/dispmanx_util.h
@@ -0,0 +1,167 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_RASPI_SHARED_DISPMANX_UTIL_H_ +#define STARBOARD_RASPI_SHARED_DISPMANX_UTIL_H_ + +#include <bcm_host.h> + +#include "starboard/common/scoped_ptr.h" +#include "starboard/configuration.h" +#include "starboard/log.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/player/video_frame_internal.h" +#include "starboard/types.h" + +namespace starboard { +namespace raspi { +namespace shared { + +class DispmanxRect : public VC_RECT_T { + public: + DispmanxRect() { vc_dispmanx_rect_set(this, 0, 0, 0, 0); } + DispmanxRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + vc_dispmanx_rect_set(this, x, y, width, height); + } +}; + +class DispmanxDisplay { + public: + DispmanxDisplay() { + bcm_host_init(); + handle_ = vc_dispmanx_display_open(0); + SB_DCHECK(handle_ != DISPMANX_NO_HANDLE); + } + ~DispmanxDisplay() { + int result = vc_dispmanx_display_close(handle_); + SB_DCHECK(result == 0); + bcm_host_deinit(); + } + + DISPMANX_DISPLAY_HANDLE_T handle() const { return handle_; } + + private: + DISPMANX_DISPLAY_HANDLE_T handle_; + + SB_DISALLOW_COPY_AND_ASSIGN(DispmanxDisplay); +}; + +class DispmanxResource { + public: + DispmanxResource() : handle_(DISPMANX_NO_HANDLE) {} + + virtual ~DispmanxResource() { + if (handle_ != DISPMANX_NO_HANDLE) { + int32_t result = vc_dispmanx_resource_delete(handle_); + SB_DCHECK(result == 0) << " result=" << result; + } + } + + DISPMANX_RESOURCE_HANDLE_T handle() const { return handle_; } + uint32_t visible_width() const { return visible_width_; } + uint32_t visible_height() const { return visible_height_; } + uint32_t width() const { return width_; } + uint32_t height() const { return height_; } + + protected: + DispmanxResource(VC_IMAGE_TYPE_T image_type, + uint32_t width, + uint32_t height, + uint32_t visible_width, + uint32_t visible_height); + + private: + DISPMANX_RESOURCE_HANDLE_T handle_; + uint32_t width_; + uint32_t height_; + uint32_t visible_width_; + uint32_t visible_height_; + + SB_DISALLOW_COPY_AND_ASSIGN(DispmanxResource); +}; + +class DispmanxYUV420Resource : public DispmanxResource { + public: + DispmanxYUV420Resource(uint32_t width, + uint32_t height, + uint32_t visible_width, + uint32_t visible_height) + : DispmanxResource(VC_IMAGE_YUV420, + width, + height, + visible_width, + visible_height) {} + + void WriteData(const void* data); + void ClearWithBlack(); +}; + +class DispmanxRGB565Resource : public DispmanxResource { + public: + DispmanxRGB565Resource(uint32_t width, + uint32_t height, + uint32_t visible_width, + uint32_t visible_height) + : DispmanxResource(VC_IMAGE_RGB565, + width, + height, + visible_width, + visible_height) {} + + void WriteData(const void* data); + void ClearWithBlack(); +}; + +class DispmanxElement { + public: + DispmanxElement(const DispmanxDisplay& display, + int32_t layer, + const DispmanxRect& dest_rect, + const DispmanxResource& src, + const DispmanxRect& src_rect); + ~DispmanxElement(); + + DISPMANX_ELEMENT_HANDLE_T handle() const { return handle_; } + void ChangeSource(const DispmanxResource& new_src); + + private: + DISPMANX_ELEMENT_HANDLE_T handle_; + + SB_DISALLOW_COPY_AND_ASSIGN(DispmanxElement); +}; + +class DispmanxVideoRenderer { + public: + typedef starboard::shared::starboard::player::VideoFrame VideoFrame; + + DispmanxVideoRenderer(const DispmanxDisplay& display, int32_t layer); + + void Update(const scoped_refptr<VideoFrame>& video_frame); + + private: + scoped_ptr<DispmanxElement> element_; + scoped_refptr<VideoFrame> frame_; + + // Used to fill the background with black if no video is playing so that the + // console does not show through. + DispmanxRGB565Resource black_frame_; + + SB_DISALLOW_COPY_AND_ASSIGN(DispmanxVideoRenderer); +}; + +} // namespace shared +} // namespace raspi +} // namespace starboard + +#endif // STARBOARD_RASPI_SHARED_DISPMANX_UTIL_H_
diff --git a/src/starboard/raspi/shared/open_max/open_max_component.cc b/src/starboard/raspi/shared/open_max/open_max_component.cc new file mode 100644 index 0000000..bb814f6c --- /dev/null +++ b/src/starboard/raspi/shared/open_max/open_max_component.cc
@@ -0,0 +1,373 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/raspi/shared/open_max/open_max_component.h" + +#include <algorithm> + +#include "starboard/configuration.h" +#include "starboard/once.h" +#include "starboard/thread.h" + +namespace starboard { +namespace raspi { +namespace shared { +namespace open_max { + +namespace { + +const int kInvalidPort = -1; + +OMX_INDEXTYPE kPortTypes[] = {OMX_IndexParamAudioInit, OMX_IndexParamVideoInit, + OMX_IndexParamImageInit, OMX_IndexParamOtherInit}; + +SbOnceControl s_open_max_initialization_once = SB_ONCE_INITIALIZER; + +void DoInitializeOpenMax() { + OMX_ERRORTYPE error = OMX_Init(); + SB_DCHECK(error == OMX_ErrorNone); +} + +void InitializeOpenMax() { + bool initialized = + SbOnce(&s_open_max_initialization_once, DoInitializeOpenMax); + SB_DCHECK(initialized); +} + +} // namespace + +OpenMaxComponent::OpenMaxComponent(const char* name) + : condition_variable_(mutex_), + handle_(NULL), + input_port_(kInvalidPort), + output_port_(kInvalidPort), + output_setting_changed_(false), + output_port_enabled_(false) { + InitializeOpenMax(); + + OMX_CALLBACKTYPE callbacks; + callbacks.EventHandler = OpenMaxComponent::EventHandler; + callbacks.EmptyBufferDone = OpenMaxComponent::EmptyBufferDone; + callbacks.FillBufferDone = OpenMaxComponent::FillBufferDone; + + OMX_ERRORTYPE error = + OMX_GetHandle(&handle_, const_cast<char*>(name), this, &callbacks); + SB_DCHECK(error == OMX_ErrorNone); + + for (size_t i = 0; i < SB_ARRAY_SIZE(kPortTypes); ++i) { + OMX_PORT_PARAM_TYPE port; + port.nSize = sizeof(OMX_PORT_PARAM_TYPE); + port.nVersion.nVersion = OMX_VERSION; + + error = OMX_GetParameter(handle_, kPortTypes[i], &port); + if (error == OMX_ErrorNone && port.nPorts == 2) { + input_port_ = port.nStartPortNumber; + output_port_ = input_port_ + 1; + SendCommandAndWaitForCompletion(OMX_CommandPortDisable, input_port_); + SendCommandAndWaitForCompletion(OMX_CommandPortDisable, output_port_); + break; + } + } + SB_CHECK(input_port_ != kInvalidPort); + SB_CHECK(output_port_ != kInvalidPort); + SB_DLOG(INFO) << "Opened \"" << name << "\" with port " << input_port_ + << " and " << output_port_; + + SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateIdle); +} + +OpenMaxComponent::~OpenMaxComponent() { + if (!handle_) { + return; + } + SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateIdle); + + SendCommandAndWaitForCompletion(OMX_CommandFlush, input_port_); + SendCommandAndWaitForCompletion(OMX_CommandFlush, output_port_); + + SendCommand(OMX_CommandPortDisable, input_port_); + for (size_t i = 0; i < input_buffers_.size(); ++i) { + OMX_ERRORTYPE error = + OMX_FreeBuffer(handle_, input_port_, input_buffers_[i]); + SB_DCHECK(error == OMX_ErrorNone); + } + WaitForCommandCompletion(); + + SendCommand(OMX_CommandPortDisable, output_port_); + for (size_t i = 0; i < output_buffers_.size(); ++i) { + OMX_ERRORTYPE error = + OMX_FreeBuffer(handle_, output_port_, output_buffers_[i]); + SB_DCHECK(error == OMX_ErrorNone); + } + output_buffers_.clear(); + WaitForCommandCompletion(); + + SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateLoaded); + OMX_FreeHandle(handle_); +} + +void OpenMaxComponent::Start() { + EnableInputPortAndAllocateBuffers(); + SendCommandAndWaitForCompletion(OMX_CommandStateSet, OMX_StateExecuting); +} + +void OpenMaxComponent::Flush() { + SendCommandAndWaitForCompletion(OMX_CommandFlush, input_port_); + SendCommandAndWaitForCompletion(OMX_CommandFlush, output_port_); +} + +void OpenMaxComponent::WriteData(const void* data, + size_t size, + SbTime timestamp) { + size_t offset = 0; + + while (offset != size) { + OMX_BUFFERHEADERTYPE* buffer_header = GetUnusedInputBuffer(); + + int size_to_append = std::min(size - offset, buffer_header->nAllocLen); + buffer_header->nOffset = 0; + buffer_header->nFilledLen = size_to_append; + buffer_header->nFlags = 0; + + buffer_header->nTimeStamp.nLowPart = timestamp; + buffer_header->nTimeStamp.nHighPart = timestamp >> 32; + + memcpy(buffer_header->pBuffer, (const char*)data + offset, size_to_append); + offset += size_to_append; + + OMX_ERRORTYPE error = OMX_EmptyThisBuffer(handle_, buffer_header); + SB_DCHECK(error == OMX_ErrorNone); + } +} + +void OpenMaxComponent::WriteEOS() { + OMX_BUFFERHEADERTYPE* buffer_header = GetUnusedInputBuffer(); + + buffer_header->nOffset = 0; + buffer_header->nFilledLen = 0; + buffer_header->nFlags = OMX_BUFFERFLAG_EOS; + + OMX_ERRORTYPE error = OMX_EmptyThisBuffer(handle_, buffer_header); + SB_DCHECK(error == OMX_ErrorNone); +} + +OMX_BUFFERHEADERTYPE* OpenMaxComponent::PeekNextOutputBuffer() { + { + ScopedLock scoped_lock(mutex_); + + if (!output_setting_changed_) { + return NULL; + } + } + + if (!output_port_enabled_) { + EnableOutputPortAndAllocateBuffer(); + } + + ScopedLock scoped_lock(mutex_); + return filled_output_buffers_.empty() ? NULL : filled_output_buffers_.front(); +} + +void OpenMaxComponent::DropNextOutputBuffer() { + OMX_BUFFERHEADERTYPE* buffer = NULL; + { + ScopedLock scoped_lock(mutex_); + SB_DCHECK(!filled_output_buffers_.empty()); + buffer = filled_output_buffers_.front(); + filled_output_buffers_.pop(); + } + buffer->nFilledLen = 0; + OMX_ERRORTYPE error = OMX_FillThisBuffer(handle_, buffer); + SB_DCHECK(error == OMX_ErrorNone); +} + +void OpenMaxComponent::SendCommand(OMX_COMMANDTYPE command, int param) { + OMX_ERRORTYPE error = OMX_SendCommand(handle_, command, param, NULL); + SB_DCHECK(error == OMX_ErrorNone); +} + +void OpenMaxComponent::WaitForCommandCompletion() { + for (;;) { + ScopedLock scoped_lock(mutex_); + for (EventDescriptions::iterator iter = event_descriptions_.begin(); + iter != event_descriptions_.end(); ++iter) { + if (iter->event == OMX_EventCmdComplete) { + event_descriptions_.erase(iter); + return; + } + // Special case for OMX_CommandStateSet. + if (iter->event == OMX_EventError && iter->data1 == OMX_ErrorSameState) { + event_descriptions_.erase(iter); + return; + } + } + condition_variable_.Wait(); + } +} + +void OpenMaxComponent::SendCommandAndWaitForCompletion(OMX_COMMANDTYPE command, + int param) { + SendCommand(command, param); + WaitForCommandCompletion(); +} + +void OpenMaxComponent::EnableInputPortAndAllocateBuffers() { + SB_DCHECK(input_buffers_.empty()); + + OMXParamPortDefinition port_definition; + GetInputPortParam(&port_definition); + if (OnEnableInputPort(&port_definition)) { + SetPortParam(port_definition); + } + + SendCommand(OMX_CommandPortEnable, input_port_); + + for (int i = 0; i < port_definition.nBufferCountActual; ++i) { + OMX_BUFFERHEADERTYPE* buffer; + OMX_ERRORTYPE error = OMX_AllocateBuffer(handle_, &buffer, input_port_, + NULL, port_definition.nBufferSize); + SB_DCHECK(error == OMX_ErrorNone); + input_buffers_.push_back(buffer); + unused_input_buffers_.push(buffer); + } + + WaitForCommandCompletion(); +} + +void OpenMaxComponent::EnableOutputPortAndAllocateBuffer() { + SB_DCHECK(!output_port_enabled_); + + GetOutputPortParam(&output_port_definition_); + if (OnEnableOutputPort(&output_port_definition_)) { + SetPortParam(output_port_definition_); + } + + SendCommand(OMX_CommandPortEnable, output_port_); + + output_buffers_.reserve(output_port_definition_.nBufferCountActual); + for (int i = 0; i < output_port_definition_.nBufferCountActual; ++i) { + OMX_BUFFERHEADERTYPE* buffer; + OMX_ERRORTYPE error = + OMX_AllocateBuffer(handle_, &buffer, output_port_, NULL, + output_port_definition_.nBufferSize); + SB_DCHECK(error == OMX_ErrorNone); + output_buffers_.push_back(buffer); + } + + WaitForCommandCompletion(); + + output_port_enabled_ = true; + + for (size_t i = 0; i < output_buffers_.size(); ++i) { + output_buffers_[i]->nFilledLen = 0; + OMX_ERRORTYPE error = OMX_FillThisBuffer(handle_, output_buffers_[i]); + SB_DCHECK(error == OMX_ErrorNone); + } +} + +OMX_BUFFERHEADERTYPE* OpenMaxComponent::GetUnusedInputBuffer() { + for (;;) { + { + ScopedLock scoped_lock(mutex_); + if (!unused_input_buffers_.empty()) { + OMX_BUFFERHEADERTYPE* buffer_header = unused_input_buffers_.front(); + unused_input_buffers_.pop(); + return buffer_header; + } + } + SbThreadSleep(kSbTimeMillisecond); + } + SB_NOTREACHED(); + return NULL; +} + +OMX_ERRORTYPE OpenMaxComponent::OnEvent(OMX_EVENTTYPE event, + OMX_U32 data1, + OMX_U32 data2, + OMX_PTR event_data) { + if (event == OMX_EventError && data1 != OMX_ErrorSameState) { + SB_NOTREACHED() << "OMX_EventError received with " << std::hex << data1 + << " " << data2; + return OMX_ErrorNone; + } + + ScopedLock scoped_lock(mutex_); + if (event == OMX_EventPortSettingsChanged && data1 == output_port_) { + output_setting_changed_ = true; + return OMX_ErrorNone; + } + EventDescription event_desc; + event_desc.event = event; + event_desc.data1 = data1; + event_desc.data2 = data2; + event_desc.event_data = event_data; + event_descriptions_.push_back(event_desc); + condition_variable_.Signal(); + + return OMX_ErrorNone; +} + +OMX_ERRORTYPE OpenMaxComponent::OnEmptyBufferDone( + OMX_BUFFERHEADERTYPE* buffer) { + ScopedLock scoped_lock(mutex_); + unused_input_buffers_.push(buffer); +} + +void OpenMaxComponent::OnFillBufferDone(OMX_BUFFERHEADERTYPE* buffer) { + ScopedLock scoped_lock(mutex_); + filled_output_buffers_.push(buffer); +} + +// static +OMX_ERRORTYPE OpenMaxComponent::EventHandler(OMX_HANDLETYPE handle, + OMX_PTR app_data, + OMX_EVENTTYPE event, + OMX_U32 data1, + OMX_U32 data2, + OMX_PTR event_data) { + SB_DCHECK(app_data != NULL); + OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data); + SB_DCHECK(handle == component->handle_); + + return component->OnEvent(event, data1, data2, event_data); +} + +// static +OMX_ERRORTYPE OpenMaxComponent::EmptyBufferDone(OMX_HANDLETYPE handle, + OMX_PTR app_data, + OMX_BUFFERHEADERTYPE* buffer) { + SB_DCHECK(app_data != NULL); + OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data); + SB_DCHECK(handle == component->handle_); + + return component->OnEmptyBufferDone(buffer); +} + +// static +OMX_ERRORTYPE OpenMaxComponent::FillBufferDone(OMX_HANDLETYPE handle, + OMX_PTR app_data, + OMX_BUFFERHEADERTYPE* buffer) { + SB_DCHECK(app_data != NULL); + OpenMaxComponent* component = reinterpret_cast<OpenMaxComponent*>(app_data); + SB_DCHECK(handle == component->handle_); + + component->OnFillBufferDone(buffer); + + return OMX_ErrorNone; +} + +} // namespace open_max +} // namespace shared +} // namespace raspi +} // namespace starboard
diff --git a/src/starboard/raspi/shared/open_max/open_max_component.h b/src/starboard/raspi/shared/open_max/open_max_component.h new file mode 100644 index 0000000..d9b5295 --- /dev/null +++ b/src/starboard/raspi/shared/open_max/open_max_component.h
@@ -0,0 +1,161 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_COMPONENT_H_ +#define STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_COMPONENT_H_ + +// OMX_SKIP64BIT is required for using VC GPU code. +#define OMX_SKIP64BIT 1 + +#include <IL/OMX_Broadcom.h> +#include <interface/vcos/vcos.h> +#include <interface/vcos/vcos_logging.h> +#include <interface/vmcs_host/vchost.h> + +#include <queue> +#include <vector> + +#include "starboard/condition_variable.h" +#include "starboard/log.h" +#include "starboard/mutex.h" +#include "starboard/shared/internal_only.h" +#include "starboard/time.h" + +namespace starboard { +namespace raspi { +namespace shared { +namespace open_max { + +template <typename ParamType, OMX_INDEXTYPE index> +struct OMXParam : public ParamType { + static const OMX_INDEXTYPE Index = index; + + OMXParam() : ParamType() { + ParamType::nSize = sizeof(ParamType); + ParamType::nVersion.nVersion = OMX_VERSION; + } +}; + +typedef OMXParam<OMX_PARAM_PORTDEFINITIONTYPE, OMX_IndexParamPortDefinition> + OMXParamPortDefinition; +typedef OMXParam<OMX_VIDEO_PARAM_PORTFORMATTYPE, OMX_IndexParamVideoPortFormat> + OMXVideoParamPortFormat; + +class OpenMaxComponent { + public: + explicit OpenMaxComponent(const char* name); + virtual ~OpenMaxComponent(); + + void Start(); + void Flush(); + + void WriteData(const void* data, size_t size, SbTime timestamp); + void WriteEOS(); + + OMX_BUFFERHEADERTYPE* PeekNextOutputBuffer(); + void DropNextOutputBuffer(); + + template <typename ParamType> + void GetInputPortParam(ParamType* param) const { + param->nPortIndex = input_port_; + OMX_ERRORTYPE error = OMX_GetParameter(handle_, ParamType::Index, param); + SB_DCHECK(error == OMX_ErrorNone) << std::hex << "OMX_GetParameter(" + << ParamType::Index + << ") failed with error " << error; + } + + template <typename ParamType> + void GetOutputPortParam(ParamType* param) const { + param->nPortIndex = output_port_; + OMX_ERRORTYPE error = OMX_GetParameter(handle_, ParamType::Index, param); + SB_DCHECK(error == OMX_ErrorNone) << std::hex << "OMX_GetParameter(" + << ParamType::Index + << ") failed with error " << error; + } + + template <typename ParamType> + void SetPortParam(const ParamType& param) const { + OMX_ERRORTYPE error = OMX_SetParameter(handle_, ParamType::Index, + const_cast<ParamType*>(¶m)); + SB_DCHECK(error == OMX_ErrorNone) << std::hex << "OMX_SetParameter(" + << ParamType::Index + << ") failed with error " << error; + } + + private: + struct EventDescription { + OMX_EVENTTYPE event; + OMX_U32 data1; + OMX_U32 data2; + OMX_PTR event_data; + }; + + typedef std::vector<EventDescription> EventDescriptions; + + virtual bool OnEnableInputPort(OMXParamPortDefinition* port_definition) { + return false; + } + virtual bool OnEnableOutputPort(OMXParamPortDefinition* port_definition) { + return false; + } + + void SendCommand(OMX_COMMANDTYPE command, int param); + void WaitForCommandCompletion(); + void SendCommandAndWaitForCompletion(OMX_COMMANDTYPE command, int param); + void EnableInputPortAndAllocateBuffers(); + void EnableOutputPortAndAllocateBuffer(); + OMX_BUFFERHEADERTYPE* GetUnusedInputBuffer(); + + OMX_ERRORTYPE OnEvent(OMX_EVENTTYPE event, + OMX_U32 data1, + OMX_U32 data2, + OMX_PTR event_data); + OMX_ERRORTYPE OnEmptyBufferDone(OMX_BUFFERHEADERTYPE* buffer); + void OnFillBufferDone(OMX_BUFFERHEADERTYPE* buffer); + + static OMX_ERRORTYPE EventHandler(OMX_HANDLETYPE handle, + OMX_PTR app_data, + OMX_EVENTTYPE event, + OMX_U32 data1, + OMX_U32 data2, + OMX_PTR event_data); + static OMX_ERRORTYPE EmptyBufferDone(OMX_HANDLETYPE handle, + OMX_PTR app_data, + OMX_BUFFERHEADERTYPE* buffer); + static OMX_ERRORTYPE FillBufferDone(OMX_HANDLETYPE handle, + OMX_PTR app_data, + OMX_BUFFERHEADERTYPE* buffer); + + Mutex mutex_; + ConditionVariable condition_variable_; + OMX_HANDLETYPE handle_; + int input_port_; + int output_port_; + bool output_setting_changed_; + EventDescriptions event_descriptions_; + std::vector<OMX_BUFFERHEADERTYPE*> input_buffers_; + std::queue<OMX_BUFFERHEADERTYPE*> unused_input_buffers_; + std::vector<OMX_BUFFERHEADERTYPE*> output_buffers_; + std::queue<OMX_BUFFERHEADERTYPE*> filled_output_buffers_; + + OMXParamPortDefinition output_port_definition_; + bool output_port_enabled_; +}; + +} // namespace open_max +} // namespace shared +} // namespace raspi +} // namespace starboard + +#endif // STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_COMPONENT_H_
diff --git a/src/starboard/raspi/shared/open_max/open_max_video_decode_component.cc b/src/starboard/raspi/shared/open_max/open_max_video_decode_component.cc new file mode 100644 index 0000000..0520c36 --- /dev/null +++ b/src/starboard/raspi/shared/open_max/open_max_video_decode_component.cc
@@ -0,0 +1,165 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/raspi/shared/open_max/open_max_video_decode_component.h" + +#include <algorithm> + +#include "starboard/configuration.h" + +namespace starboard { +namespace raspi { +namespace shared { +namespace open_max { + +namespace { + +const char kVideoDecodeComponentName[] = "OMX.broadcom.video_decode"; +const size_t kResourcePoolSize = 12; +const size_t kOMXOutputBufferCount = 4; +const int kMaxFrameWidth = 1920; +const int kMaxFrameHeight = 1088; +const size_t kMaxVideoFrameSize = kMaxFrameWidth * kMaxFrameHeight * 3 / 2; + +} // namespace + +typedef OpenMaxVideoDecodeComponent::VideoFrame VideoFrame; + +VideoFrameResourcePool::VideoFrameResourcePool(size_t max_number_of_resources) + : max_number_of_resources_(max_number_of_resources), + number_of_resources_(0), + last_frame_height_(0) {} + +VideoFrameResourcePool::~VideoFrameResourcePool() { + for (ResourceMap::iterator iter = resource_map_.begin(); + iter != resource_map_.end(); ++iter) { + while (!iter->second.empty()) { + delete iter->second.front(); + iter->second.pop(); + --number_of_resources_; + } + } + SB_DCHECK(number_of_resources_ == 0) << number_of_resources_; +} + +DispmanxYUV420Resource* VideoFrameResourcePool::Alloc(int width, + int height, + int visible_width, + int visible_height) { + ScopedLock scoped_lock(mutex_); + + last_frame_height_ = height; + + ResourceMap::iterator iter = resource_map_.find(height); + if (iter != resource_map_.end() && !iter->second.empty()) { + DispmanxYUV420Resource* resource = iter->second.front(); + iter->second.pop(); + return resource; + } + + if (number_of_resources_ >= max_number_of_resources_) { + return NULL; + } + + ++number_of_resources_; + return new DispmanxYUV420Resource(width, height, visible_width, + visible_height); +} + +void VideoFrameResourcePool::Free(DispmanxYUV420Resource* resource) { + ScopedLock scoped_lock(mutex_); + if (resource->height() != last_frame_height_) { + // The video has adapted, free the resource as it won't be reused any soon. + delete resource; + --number_of_resources_; + return; + } + resource_map_[resource->height()].push(resource); +} + +// static +void VideoFrameResourcePool::DisposeDispmanxYUV420Resource( + void* context, + void* dispmanx_resource) { + SB_DCHECK(context != NULL); + SB_DCHECK(dispmanx_resource != NULL); + VideoFrameResourcePool* pool = + reinterpret_cast<VideoFrameResourcePool*>(context); + pool->Free(reinterpret_cast<DispmanxYUV420Resource*>(dispmanx_resource)); + pool->Release(); +} + +OpenMaxVideoDecodeComponent::OpenMaxVideoDecodeComponent() + : OpenMaxComponent(kVideoDecodeComponentName), + resource_pool_(new VideoFrameResourcePool(kResourcePoolSize)) {} + +scoped_refptr<VideoFrame> OpenMaxVideoDecodeComponent::ReadVideoFrame() { + if (OMX_BUFFERHEADERTYPE* buffer = PeekNextOutputBuffer()) { + if (scoped_refptr<VideoFrame> frame = CreateVideoFrame(buffer)) { + DropNextOutputBuffer(); + return frame; + } + } + return NULL; +} + +scoped_refptr<VideoFrame> OpenMaxVideoDecodeComponent::CreateVideoFrame( + OMX_BUFFERHEADERTYPE* buffer) { + scoped_refptr<VideoFrame> frame; + if (buffer->nFlags & OMX_BUFFERFLAG_EOS) { + frame = VideoFrame::CreateEOSFrame(); + } else { + OMX_VIDEO_PORTDEFINITIONTYPE& video_definition = + output_port_definition_.format.video; + DispmanxYUV420Resource* resource = resource_pool_->Alloc( + video_definition.nStride, video_definition.nSliceHeight, + video_definition.nFrameWidth, video_definition.nFrameHeight); + if (!resource) { + return NULL; + } + + resource->WriteData(buffer->pBuffer); + + SbMediaTime timestamp = ((buffer->nTimeStamp.nHighPart * 0x100000000ull) + + buffer->nTimeStamp.nLowPart) * + kSbMediaTimeSecond / kSbTimeSecond; + + resource_pool_->AddRef(); + frame = new VideoFrame( + video_definition.nFrameWidth, video_definition.nFrameHeight, timestamp, + resource, resource_pool_, + &VideoFrameResourcePool::DisposeDispmanxYUV420Resource); + } + return frame; +} + +bool OpenMaxVideoDecodeComponent::OnEnableOutputPort( + OMXParamPortDefinition* port_definition) { + SB_DCHECK(port_definition); + + output_port_definition_ = *port_definition; + SB_DCHECK(port_definition->format.video.eColorFormat == + OMX_COLOR_FormatYUV420PackedPlanar); + port_definition->format.video.eColorFormat = + OMX_COLOR_FormatYUV420PackedPlanar; + port_definition->nBufferCountActual = kOMXOutputBufferCount; + port_definition->nBufferSize = + std::max(port_definition->nBufferSize, kMaxVideoFrameSize); + return true; +} + +} // namespace open_max +} // namespace shared +} // namespace raspi +} // namespace starboard
diff --git a/src/starboard/raspi/shared/open_max/open_max_video_decode_component.h b/src/starboard/raspi/shared/open_max/open_max_video_decode_component.h new file mode 100644 index 0000000..94b9cda --- /dev/null +++ b/src/starboard/raspi/shared/open_max/open_max_video_decode_component.h
@@ -0,0 +1,84 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_VIDEO_DECODE_COMPONENT_H_ +#define STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_VIDEO_DECODE_COMPONENT_H_ + +#include <map> +#include <queue> + +#include "starboard/common/ref_counted.h" +#include "starboard/raspi/shared/dispmanx_util.h" +#include "starboard/raspi/shared/open_max/open_max_component.h" + +namespace starboard { +namespace raspi { +namespace shared { +namespace open_max { + +class VideoFrameResourcePool + : public RefCountedThreadSafe<VideoFrameResourcePool> { + public: + explicit VideoFrameResourcePool(size_t max_number_of_resources); + ~VideoFrameResourcePool(); + + DispmanxYUV420Resource* Alloc(int width, + int height, + int visible_width, + int visible_height); + void Free(DispmanxYUV420Resource* resource); + + static void DisposeDispmanxYUV420Resource(void* context, + void* dispmanx_resource); + + private: + typedef std::queue<DispmanxYUV420Resource*> ResourceQueue; + // Map frame height to resource handles. + typedef std::map<int, ResourceQueue> ResourceMap; + + const size_t max_number_of_resources_; + + Mutex mutex_; + size_t number_of_resources_; + int last_frame_height_; + ResourceMap resource_map_; +}; + +// Encapsulate a "OMX.broadcom.video_decode" component. Note that member +// functions of this class is expected to be called from ANY threads as this +// class works with the VideoDecoder filter, the OpenMAX component, and also +// manages the disposition of Dispmanx resource. +class OpenMaxVideoDecodeComponent : public OpenMaxComponent { + public: + typedef starboard::shared::starboard::player::VideoFrame VideoFrame; + + OpenMaxVideoDecodeComponent(); + + scoped_refptr<VideoFrame> ReadVideoFrame(); + + private: + scoped_refptr<VideoFrame> CreateVideoFrame(OMX_BUFFERHEADERTYPE* buffer); + + bool OnEnableOutputPort(OMXParamPortDefinition* port_definition) SB_OVERRIDE; + + scoped_refptr<VideoFrameResourcePool> resource_pool_; + OMXParamPortDefinition output_port_definition_; +}; + +} // namespace open_max +} // namespace shared +} // namespace raspi +} // namespace starboard + +#endif // STARBOARD_RASPI_SHARED_OPEN_MAX_OPEN_MAX_VIDEO_DECODE_COMPONENT_H_
diff --git a/src/starboard/raspi/shared/open_max/video_decoder.cc b/src/starboard/raspi/shared/open_max/video_decoder.cc new file mode 100644 index 0000000..a238db7 --- /dev/null +++ b/src/starboard/raspi/shared/open_max/video_decoder.cc
@@ -0,0 +1,94 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/raspi/shared/open_max/video_decoder.h" + +#include "starboard/log.h" + +namespace starboard { +namespace raspi { +namespace shared { +namespace open_max { + +using starboard::shared::starboard::player::VideoFrame; + +VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec) + : host_(NULL), stream_ended_(false) { + SB_DCHECK(video_codec == kSbMediaVideoCodecH264); + + OMXVideoParamPortFormat port_format; + component_.GetInputPortParam(&port_format); + port_format.eCompressionFormat = OMX_VIDEO_CodingAVC; + component_.SetPortParam(port_format); + + component_.Start(); +} + +VideoDecoder::~VideoDecoder() {} + +void VideoDecoder::SetHost(Host* host) { + SB_DCHECK(host != NULL); + SB_DCHECK(host_ == NULL); + host_ = host; +} + +void VideoDecoder::WriteInputBuffer(const InputBuffer& input_buffer) { + SB_DCHECK(host_ != NULL); + + if (stream_ended_) { + SB_LOG(ERROR) << "WriteInputFrame() was called after WriteEndOfStream()."; + return; + } + component_.WriteData(input_buffer.data(), input_buffer.size(), + input_buffer.pts() * kSbTimeSecond / kSbMediaTimeSecond); + if (scoped_refptr<VideoFrame> frame = component_.ReadVideoFrame()) { + host_->OnDecoderStatusUpdate(kNeedMoreInput, frame); + } else { + // Call the callback with NULL frame to ensure that the host know that more + // data is expected. + host_->OnDecoderStatusUpdate(kNeedMoreInput, NULL); + } +} + +void VideoDecoder::WriteEndOfStream() { + SB_DCHECK(!stream_ended_); + stream_ended_ = true; + component_.WriteEOS(); +} + +void VideoDecoder::Reset() { + stream_ended_ = false; + component_.Flush(); +} + +} // namespace open_max +} // namespace shared +} // namespace raspi + +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +// static +VideoDecoder* VideoDecoder::Create(SbMediaVideoCodec video_codec) { + return new raspi::shared::open_max::VideoDecoder(video_codec); +} + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared + +} // namespace starboard
diff --git a/src/starboard/raspi/shared/open_max/video_decoder.h b/src/starboard/raspi/shared/open_max/video_decoder.h new file mode 100644 index 0000000..de684af --- /dev/null +++ b/src/starboard/raspi/shared/open_max/video_decoder.h
@@ -0,0 +1,56 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_RASPI_SHARED_OPEN_MAX_VIDEO_DECODER_H_ +#define STARBOARD_RASPI_SHARED_OPEN_MAX_VIDEO_DECODER_H_ + +#include "starboard/media.h" +#include "starboard/raspi/shared/open_max/open_max_video_decode_component.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/player/filter/video_decoder_internal.h" + +namespace starboard { +namespace raspi { +namespace shared { +namespace open_max { + +class VideoDecoder + : public starboard::shared::starboard::player::filter::VideoDecoder { + public: + typedef starboard::shared::starboard::player::InputBuffer InputBuffer; + + explicit VideoDecoder(SbMediaVideoCodec video_codec); + ~VideoDecoder() SB_OVERRIDE; + + void SetHost(Host* host) SB_OVERRIDE; + void WriteInputBuffer(const InputBuffer& input_buffer) SB_OVERRIDE; + void WriteEndOfStream() SB_OVERRIDE; + void Reset() SB_OVERRIDE; + + private: + OpenMaxVideoDecodeComponent component_; + + // These variables will be initialized inside ctor or SetHost() and will not + // be changed during the life time of this class. + Host* host_; + + bool stream_ended_; +}; + +} // namespace open_max +} // namespace shared +} // namespace raspi +} // namespace starboard + +#endif // STARBOARD_RASPI_SHARED_OPEN_MAX_VIDEO_DECODER_H_
diff --git a/src/starboard/raspi/shared/thread_create_priority.cc b/src/starboard/raspi/shared/thread_create_priority.cc new file mode 100644 index 0000000..b36cd1f --- /dev/null +++ b/src/starboard/raspi/shared/thread_create_priority.cc
@@ -0,0 +1,71 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/pthread/thread_create_priority.h" + +#include <sched.h> + +#include "starboard/log.h" + +namespace starboard { +namespace shared { +namespace pthread { + +#if SB_HAS(THREAD_PRIORITY_SUPPORT) +void ThreadSetPriority(SbThreadPriority priority) { + // Note that use of sched_setscheduler() has been found to be more reliably + // supported than pthread_setschedparam(), so we are using that. + + // Use different schedulers according to priority. This is preferred over + // using SCHED_RR for all threads because the scheduler time slice is too + // high (defaults to 100ms) for the desired threading behavior. + struct sched_param thread_sched_param; + int result = 0; + + thread_sched_param.sched_priority = 0; + switch (priority) { + case kSbThreadPriorityLowest: + case kSbThreadPriorityLow: + result = sched_setscheduler(0, SCHED_IDLE, &thread_sched_param); + break; + case kSbThreadNoPriority: + case kSbThreadPriorityNormal: + result = sched_setscheduler(0, SCHED_OTHER, &thread_sched_param); + break; + case kSbThreadPriorityHigh: + thread_sched_param.sched_priority = 1; + result = sched_setscheduler(0, SCHED_RR, &thread_sched_param); + break; + case kSbThreadPriorityHighest: + thread_sched_param.sched_priority = 2; + result = sched_setscheduler(0, SCHED_RR, &thread_sched_param); + break; + case kSbThreadPriorityRealTime: + thread_sched_param.sched_priority = 3; + result = sched_setscheduler(0, SCHED_RR, &thread_sched_param); + break; + default: + SB_NOTREACHED(); + break; + } + + if (result != 0) { + SB_NOTREACHED(); + } +} +#endif // SB_HAS(THREAD_PRIORITY_SUPPORT) + +} // namespace pthread +} // namespace shared +} // namespace starboard
diff --git a/src/starboard/raspi/shared/window_internal.cc b/src/starboard/raspi/shared/window_internal.cc index 532ecd4..cb03c2d 100644 --- a/src/starboard/raspi/shared/window_internal.cc +++ b/src/starboard/raspi/shared/window_internal.cc
@@ -14,21 +14,19 @@ #include "starboard/raspi/shared/window_internal.h" -#include <bcm_host.h> - #include "starboard/log.h" namespace { const int32_t kLayer = 0; -const DISPMANX_RESOURCE_HANDLE_T kResource = DISPMANX_NO_HANDLE; } // namespace -SbWindowPrivate::SbWindowPrivate(DISPMANX_DISPLAY_HANDLE_T display, - const SbWindowOptions* options) - : display(display), element(DISPMANX_NO_HANDLE) { - VC_RECT_T destination_rect; - VC_RECT_T source_rect; +using starboard::raspi::shared::DispmanxDisplay; +using starboard::raspi::shared::DispmanxElement; +using starboard::raspi::shared::DispmanxRect; +using starboard::raspi::shared::DispmanxResource; +SbWindowPrivate::SbWindowPrivate(const DispmanxDisplay& display, + const SbWindowOptions* options) { uint32_t window_width = 0; uint32_t window_height = 0; if (options && options->size.width > 0 && options->size.height > 0) { @@ -41,41 +39,23 @@ SB_DCHECK(result >= 0); } - destination_rect.x = 0; - destination_rect.y = 0; - destination_rect.width = window_width; - destination_rect.height = window_height; - - source_rect.x = 0; - source_rect.y = 0; - // This shift is part of the examples, but unexplained. It appears to work. - source_rect.width = window_width << 16; - source_rect.height = window_height << 16; - + DispmanxRect destination_rect(0, 0, window_width, window_height); + // The "<< 16"s are part of the examples, but unexplained. It appears to work. + DispmanxRect source_rect(0, 0, window_width << 16, window_height << 16); + // The window doesn't have an image resource associated with it. + DispmanxResource resource; // Creating a window (called an "element" here, created by adding it to the // display) must happen within an "update", which seems to represent a sort of // window manager transaction. - DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0 /*screen*/); - SB_DCHECK(update != DISPMANX_NO_HANDLE); - element = vc_dispmanx_element_add(update, display, kLayer, &destination_rect, - kResource, &source_rect, - DISPMANX_PROTECTION_NONE, NULL /*alpha*/, - NULL /*clamp*/, DISPMANX_NO_ROTATE); - SB_DCHECK(element != DISPMANX_NO_HANDLE); - int32_t result = vc_dispmanx_update_submit_sync(update); - SB_DCHECK(result == 0) << " result=" << result; - + element.reset(new DispmanxElement(display, kLayer, destination_rect, resource, + source_rect)); // We can then populate this struct, a pointer to which is what EGL expects as // a "native window" handle. - window.element = element; + window.element = element->handle(); window.width = window_width; window.height = window_height; } SbWindowPrivate::~SbWindowPrivate() { - DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0 /*screen*/); - int32_t result = vc_dispmanx_element_remove(update, element); - SB_DCHECK(result == 0) << " result=" << result; - vc_dispmanx_update_submit_sync(update); - element = DISPMANX_NO_HANDLE; + element.reset(); }
diff --git a/src/starboard/raspi/shared/window_internal.h b/src/starboard/raspi/shared/window_internal.h index 06a77d5..3f51a8a 100644 --- a/src/starboard/raspi/shared/window_internal.h +++ b/src/starboard/raspi/shared/window_internal.h
@@ -15,19 +15,19 @@ #ifndef STARBOARD_RASPI_SHARED_WINDOW_INTERNAL_H_ #define STARBOARD_RASPI_SHARED_WINDOW_INTERNAL_H_ -#include <bcm_host.h> #include <EGL/egl.h> +#include "starboard/common/scoped_ptr.h" +#include "starboard/raspi/shared/dispmanx_util.h" #include "starboard/shared/internal_only.h" #include "starboard/window.h" struct SbWindowPrivate { - SbWindowPrivate(DISPMANX_DISPLAY_HANDLE_T display, + SbWindowPrivate(const starboard::raspi::shared::DispmanxDisplay& display, const SbWindowOptions* options); ~SbWindowPrivate(); - DISPMANX_DISPLAY_HANDLE_T display; - DISPMANX_ELEMENT_HANDLE_T element; + starboard::scoped_ptr<starboard::raspi::shared::DispmanxElement> element; EGL_DISPMANX_WINDOW_T window; };
diff --git a/src/starboard/shared/alsa/alsa_util.cc b/src/starboard/shared/alsa/alsa_util.cc index c713015..0568156 100644 --- a/src/starboard/shared/alsa/alsa_util.cc +++ b/src/starboard/shared/alsa/alsa_util.cc
@@ -35,6 +35,9 @@ namespace { +const snd_pcm_uframes_t kSilenceThresholdInFrames = 256U; +const snd_pcm_uframes_t kStartThresholdInFrames = 1024U; + template <typename T, typename CloseFunc> class AutoClose { public: @@ -145,19 +148,20 @@ frames_per_request); ALSA_CHECK(error, snd_pcm_sw_params_set_avail_min, NULL); - error = - snd_pcm_sw_params_set_silence_threshold(playback_handle, sw_params, 256U); + error = snd_pcm_sw_params_set_silence_threshold(playback_handle, sw_params, + kSilenceThresholdInFrames); ALSA_CHECK(error, snd_pcm_sw_params_set_silence_threshold, NULL); + error = snd_pcm_sw_params_set_start_threshold(playback_handle, sw_params, + kStartThresholdInFrames); + ALSA_CHECK(error, snd_pcm_sw_params_set_start_threshold, NULL); + error = snd_pcm_sw_params(playback_handle, sw_params); ALSA_CHECK(error, snd_pcm_sw_params, NULL); error = snd_pcm_prepare(playback_handle); ALSA_CHECK(error, snd_pcm_prepare, NULL); - error = snd_pcm_start(playback_handle); - ALSA_CHECK(error, snd_pcm_start, NULL); - return playback_handle.Detach(); }
diff --git a/src/starboard/shared/directfb/blitter_destroy_swap_chain.cc b/src/starboard/shared/directfb/blitter_destroy_swap_chain.cc index ba9d317..2f9505b 100644 --- a/src/starboard/shared/directfb/blitter_destroy_swap_chain.cc +++ b/src/starboard/shared/directfb/blitter_destroy_swap_chain.cc
@@ -26,5 +26,7 @@ swap_chain->render_target.surface->Release(swap_chain->render_target.surface); + delete swap_chain; + return true; }
diff --git a/src/starboard/shared/dlmalloc/memory_map.cc b/src/starboard/shared/dlmalloc/memory_map.cc index 59730fc..c5d3829 100644 --- a/src/starboard/shared/dlmalloc/memory_map.cc +++ b/src/starboard/shared/dlmalloc/memory_map.cc
@@ -13,9 +13,11 @@ // limitations under the License. #include "starboard/memory.h" - +#include "starboard/shared/starboard/memory_reporter_internal.h" #include "starboard/shared/dlmalloc/page_internal.h" void* SbMemoryMap(int64_t size_bytes, int flags, const char* name) { - return SbPageMap(size_bytes, flags, name); + void* memory = SbPageMap(size_bytes, flags, name); + SbMemoryReporterReportMappedMemory(memory, size_bytes); + return memory; }
diff --git a/src/starboard/shared/dlmalloc/memory_unmap.cc b/src/starboard/shared/dlmalloc/memory_unmap.cc index ff45918..dc32164 100644 --- a/src/starboard/shared/dlmalloc/memory_unmap.cc +++ b/src/starboard/shared/dlmalloc/memory_unmap.cc
@@ -13,9 +13,10 @@ // limitations under the License. #include "starboard/memory.h" - +#include "starboard/shared/starboard/memory_reporter_internal.h" #include "starboard/shared/dlmalloc/page_internal.h" bool SbMemoryUnmap(void* virtual_address, int64_t size_bytes) { + SbMemoryReporterReportUnmappedMemory(virtual_address, size_bytes); return SbPageUnmap(virtual_address, size_bytes); }
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc index 00e98e6..1444180 100644 --- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc +++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.cc
@@ -14,6 +14,7 @@ #include "starboard/shared/ffmpeg/ffmpeg_audio_decoder.h" +#include "starboard/audio_sink.h" #include "starboard/log.h" namespace starboard { @@ -22,15 +23,23 @@ namespace { -// The required output format for the decoder is interleaved float. However -// the output of the ffmpeg decoder can be in other formats. So libavresample -// is used to convert the output into the required format. -void ConvertToInterleavedFloat(int source_sample_format, - int channel_layout, - int sample_rate, - int samples_per_channel, - uint8_t** input_buffer, - uint8_t* output_buffer) { +SbMediaAudioSampleType GetSupportedSampleType() { + if (SbAudioSinkIsAudioSampleTypeSupported(kSbMediaAudioSampleTypeFloat32)) { + return kSbMediaAudioSampleTypeFloat32; + } + return kSbMediaAudioSampleTypeInt16; +} + +// The required output format and the output of the ffmpeg decoder can be +// different. In this case libavresample is used to convert the ffmpeg output +// into the required format. +void ConvertSamples(int source_sample_format, + int target_sample_format, + int channel_layout, + int sample_rate, + int samples_per_channel, + uint8_t** input_buffer, + uint8_t* output_buffer) { AVAudioResampleContext* context = avresample_alloc_context(); SB_DCHECK(context != NULL); @@ -39,7 +48,7 @@ av_opt_set_int(context, "in_sample_rate", sample_rate, 0); av_opt_set_int(context, "out_sample_rate", sample_rate, 0); av_opt_set_int(context, "in_sample_fmt", source_sample_format, 0); - av_opt_set_int(context, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0); + av_opt_set_int(context, "out_sample_fmt", target_sample_format, 0); av_opt_set_int(context, "internal_sample_fmt", source_sample_format, 0); int result = avresample_open(context); @@ -58,7 +67,8 @@ AudioDecoder::AudioDecoder(SbMediaAudioCodec audio_codec, const SbMediaAudioHeader& audio_header) - : codec_context_(NULL), + : sample_type_(GetSupportedSampleType()), + codec_context_(NULL), av_frame_(NULL), stream_ended_(false), audio_header_(audio_header) { @@ -72,7 +82,7 @@ } void AudioDecoder::Decode(const InputBuffer& input_buffer, - std::vector<float>* output) { + std::vector<uint8_t>* output) { SB_CHECK(output != NULL); SB_CHECK(codec_context_ != NULL); @@ -105,11 +115,16 @@ audio_header_.samples_per_second = codec_context_->sample_rate; if (decoded_audio_size > 0) { - output->resize(decoded_audio_size / sizeof(float)); - ConvertToInterleavedFloat( - codec_context_->sample_fmt, codec_context_->channel_layout, - audio_header_.samples_per_second, av_frame_->nb_samples, - av_frame_->extended_data, reinterpret_cast<uint8_t*>(&(*output)[0])); + output->resize(codec_context_->channels * av_frame_->nb_samples * + (sample_type_ == kSbMediaAudioSampleTypeInt16 ? 2 : 4)); + if (codec_context_->sample_fmt == codec_context_->request_sample_fmt) { + memcpy(&(*output)[0], av_frame_->extended_data, output->size()); + } else { + ConvertSamples( + codec_context_->sample_fmt, codec_context_->request_sample_fmt, + codec_context_->channel_layout, audio_header_.samples_per_second, + av_frame_->nb_samples, av_frame_->extended_data, &(*output)[0]); + } } else { // TODO: Consider fill it with silence. SB_LOG(ERROR) << "Decoded audio frame is empty."; @@ -127,7 +142,11 @@ stream_ended_ = false; } -int AudioDecoder::GetSamplesPerSecond() { +SbMediaAudioSampleType AudioDecoder::GetSampleType() const { + return sample_type_; +} + +int AudioDecoder::GetSamplesPerSecond() const { return audio_header_.samples_per_second; } @@ -144,7 +163,11 @@ codec_context_->codec_type = AVMEDIA_TYPE_AUDIO; codec_context_->codec_id = AV_CODEC_ID_AAC; // Request_sample_fmt is set by us, but sample_fmt is set by the decoder. - codec_context_->request_sample_fmt = AV_SAMPLE_FMT_FLT; // interleaved float + if (sample_type_ == kSbMediaAudioSampleTypeInt16) { + codec_context_->request_sample_fmt = AV_SAMPLE_FMT_S16; + } else { + codec_context_->request_sample_fmt = AV_SAMPLE_FMT_FLT; + } codec_context_->channels = audio_header_.number_of_channels; codec_context_->sample_rate = audio_header_.samples_per_second; @@ -190,6 +213,7 @@ namespace starboard { namespace player { +namespace filter { // static AudioDecoder* AudioDecoder::Create(SbMediaAudioCodec audio_codec, @@ -203,6 +227,7 @@ return NULL; } +} // namespace filter } // namespace player } // namespace starboard
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h index eac65a2..c2a78db 100644 --- a/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h +++ b/src/starboard/shared/ffmpeg/ffmpeg_audio_decoder.h
@@ -20,13 +20,13 @@ #include "starboard/media.h" #include "starboard/shared/ffmpeg/ffmpeg_common.h" #include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/player/audio_decoder_internal.h" +#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h" namespace starboard { namespace shared { namespace ffmpeg { -class AudioDecoder : public starboard::player::AudioDecoder { +class AudioDecoder : public starboard::player::filter::AudioDecoder { public: typedef starboard::player::InputBuffer InputBuffer; @@ -35,10 +35,11 @@ ~AudioDecoder() SB_OVERRIDE; void Decode(const InputBuffer& input_buffer, - std::vector<float>* output) SB_OVERRIDE; + std::vector<uint8_t>* output) SB_OVERRIDE; void WriteEndOfStream() SB_OVERRIDE; void Reset() SB_OVERRIDE; - int GetSamplesPerSecond() SB_OVERRIDE; + SbMediaAudioSampleType GetSampleType() const SB_OVERRIDE; + int GetSamplesPerSecond() const SB_OVERRIDE; bool is_valid() const { return codec_context_ != NULL; } @@ -46,6 +47,7 @@ void InitializeCodec(); void TeardownCodec(); + SbMediaAudioSampleType sample_type_; AVCodecContext* codec_context_; AVFrame* av_frame_;
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc index 6c7aec8..2e05ee8 100644 --- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc +++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.cc
@@ -78,11 +78,13 @@ frame->height = codec_context->height; frame->format = codec_context->pix_fmt; + frame->reordered_opaque = codec_context->reordered_opaque; + return 0; } void ReleaseBuffer(AVCodecContext*, AVFrame* frame) { - SbMemoryFree(frame->opaque); + SbMemoryDeallocate(frame->opaque); frame->opaque = NULL; // The FFmpeg API expects us to zero the data pointers in this callback. @@ -142,14 +144,16 @@ } void VideoDecoder::Reset() { - SB_DCHECK(host_ != NULL); - // Join the thread to ensure that all callbacks in process are finished. if (SbThreadIsValid(decoder_thread_)) { queue_.Put(Event(kReset)); SbThreadJoin(decoder_thread_, NULL); } + if (codec_context_ != NULL) { + avcodec_flush_buffers(codec_context_); + } + decoder_thread_ = kSbThreadInvalid; stream_ended_ = false; } @@ -178,6 +182,7 @@ packet.data = const_cast<uint8_t*>(event.input_buffer.data()); packet.size = event.input_buffer.size(); packet.pts = event.input_buffer.pts(); + codec_context_->reordered_opaque = packet.pts; DecodePacket(&packet); host_->OnDecoderStatusUpdate(kNeedMoreInput, NULL); @@ -192,8 +197,7 @@ packet.pts = 0; } while (DecodePacket(&packet)); - VideoFrame frame = VideoFrame::CreateEOSFrame(); - host_->OnDecoderStatusUpdate(kBufferFull, &frame); + host_->OnDecoderStatusUpdate(kBufferFull, VideoFrame::CreateEOSFrame()); } } } @@ -218,10 +222,11 @@ int pitch = AlignUp(av_frame_->width, kAlignment * 2); - VideoFrame frame = VideoFrame::CreateYV12Frame( - av_frame_->width, av_frame_->height, pitch, av_frame_->pkt_pts, - av_frame_->data[0], av_frame_->data[1], av_frame_->data[2]); - host_->OnDecoderStatusUpdate(kBufferFull, &frame); + scoped_refptr<VideoFrame> frame = VideoFrame::CreateYV12Frame( + av_frame_->width, av_frame_->height, pitch, + codec_context_->reordered_opaque, av_frame_->data[0], av_frame_->data[1], + av_frame_->data[2]); + host_->OnDecoderStatusUpdate(kBufferFull, frame); return true; } @@ -290,6 +295,7 @@ namespace starboard { namespace player { +namespace filter { // static VideoDecoder* VideoDecoder::Create(SbMediaVideoCodec video_codec) { @@ -301,6 +307,7 @@ return NULL; } +} // namespace filter } // namespace player } // namespace starboard
diff --git a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h index 6bcd6d3..6fcc2f1 100644 --- a/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h +++ b/src/starboard/shared/ffmpeg/ffmpeg_video_decoder.h
@@ -20,8 +20,8 @@ #include "starboard/queue.h" #include "starboard/shared/ffmpeg/ffmpeg_common.h" #include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/player/filter/video_decoder_internal.h" #include "starboard/shared/starboard/player/input_buffer_internal.h" -#include "starboard/shared/starboard/player/video_decoder_internal.h" #include "starboard/shared/starboard/player/video_frame_internal.h" #include "starboard/thread.h" @@ -29,12 +29,12 @@ namespace shared { namespace ffmpeg { -class VideoDecoder : public starboard::player::VideoDecoder { +class VideoDecoder : public starboard::player::filter::VideoDecoder { public: typedef starboard::player::InputBuffer InputBuffer; typedef starboard::player::VideoFrame VideoFrame; - explicit VideoDecoder(SbMediaVideoCodec); + explicit VideoDecoder(SbMediaVideoCodec video_codec); ~VideoDecoder() SB_OVERRIDE; void SetHost(Host* host) SB_OVERRIDE;
diff --git a/src/starboard/shared/gcc/atomic_gcc_public.h b/src/starboard/shared/gcc/atomic_gcc_public.h index 655d2be..9c7afc6 100644 --- a/src/starboard/shared/gcc/atomic_gcc_public.h +++ b/src/starboard/shared/gcc/atomic_gcc_public.h
@@ -24,7 +24,7 @@ extern "C" { #endif -SB_C_FORCE_INLINE SbAtomic32 +static SB_C_FORCE_INLINE SbAtomic32 SbAtomicNoBarrier_CompareAndSwap(volatile SbAtomic32* ptr, SbAtomic32 old_value, SbAtomic32 new_value) { @@ -37,7 +37,7 @@ return prev_value; } -SB_C_FORCE_INLINE SbAtomic32 +static SB_C_FORCE_INLINE SbAtomic32 SbAtomicNoBarrier_Exchange(volatile SbAtomic32* ptr, SbAtomic32 new_value) { SbAtomic32 old_value; do { @@ -46,13 +46,14 @@ return old_value; } -SB_C_FORCE_INLINE SbAtomic32 +static SB_C_FORCE_INLINE SbAtomic32 SbAtomicNoBarrier_Increment(volatile SbAtomic32* ptr, SbAtomic32 increment) { return SbAtomicBarrier_Increment(ptr, increment); } -SB_C_FORCE_INLINE SbAtomic32 SbAtomicBarrier_Increment(volatile SbAtomic32* ptr, - SbAtomic32 increment) { +static SB_C_FORCE_INLINE SbAtomic32 +SbAtomicBarrier_Increment(volatile SbAtomic32* ptr, + SbAtomic32 increment) { for (;;) { // Atomic exchange the old value with an incremented one. SbAtomic32 old_value = *ptr; @@ -65,7 +66,7 @@ } } -SB_C_FORCE_INLINE SbAtomic32 +static SB_C_FORCE_INLINE SbAtomic32 SbAtomicAcquire_CompareAndSwap(volatile SbAtomic32* ptr, SbAtomic32 old_value, SbAtomic32 new_value) { @@ -74,47 +75,47 @@ return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value); } -SB_C_FORCE_INLINE SbAtomic32 +static SB_C_FORCE_INLINE SbAtomic32 SbAtomicRelease_CompareAndSwap(volatile SbAtomic32* ptr, SbAtomic32 old_value, SbAtomic32 new_value) { return SbAtomicNoBarrier_CompareAndSwap(ptr, old_value, new_value); } -SB_C_FORCE_INLINE void SbAtomicMemoryBarrier() { +static SB_C_FORCE_INLINE void SbAtomicMemoryBarrier() { __sync_synchronize(); } -SB_C_FORCE_INLINE void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr, - SbAtomic32 value) { +static SB_C_FORCE_INLINE void SbAtomicNoBarrier_Store(volatile SbAtomic32* ptr, + SbAtomic32 value) { *ptr = value; } -SB_C_FORCE_INLINE void SbAtomicAcquire_Store(volatile SbAtomic32* ptr, - SbAtomic32 value) { +static SB_C_FORCE_INLINE void SbAtomicAcquire_Store(volatile SbAtomic32* ptr, + SbAtomic32 value) { *ptr = value; SbAtomicMemoryBarrier(); } -SB_C_FORCE_INLINE void SbAtomicRelease_Store(volatile SbAtomic32* ptr, - SbAtomic32 value) { +static SB_C_FORCE_INLINE void SbAtomicRelease_Store(volatile SbAtomic32* ptr, + SbAtomic32 value) { SbAtomicMemoryBarrier(); *ptr = value; } -SB_C_FORCE_INLINE SbAtomic32 +static SB_C_FORCE_INLINE SbAtomic32 SbAtomicNoBarrier_Load(volatile const SbAtomic32* ptr) { return *ptr; } -SB_C_FORCE_INLINE SbAtomic32 +static SB_C_FORCE_INLINE SbAtomic32 SbAtomicAcquire_Load(volatile const SbAtomic32* ptr) { SbAtomic32 value = *ptr; SbAtomicMemoryBarrier(); return value; } -SB_C_FORCE_INLINE SbAtomic32 +static SB_C_FORCE_INLINE SbAtomic32 SbAtomicRelease_Load(volatile const SbAtomic32* ptr) { SbAtomicMemoryBarrier(); return *ptr; @@ -122,7 +123,7 @@ // 64-bit atomic operations (only available on 64-bit processors). #if SB_HAS(64_BIT_ATOMICS) -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicNoBarrier_CompareAndSwap64(volatile SbAtomic64* ptr, SbAtomic64 old_value, SbAtomic64 new_value) { @@ -135,7 +136,7 @@ return prev_value; } -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicNoBarrier_Exchange64(volatile SbAtomic64* ptr, SbAtomic64 new_value) { SbAtomic64 old_value; do { @@ -144,12 +145,12 @@ return old_value; } -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicNoBarrier_Increment64(volatile SbAtomic64* ptr, SbAtomic64 increment) { return SbAtomicBarrier_Increment64(ptr, increment); } -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicBarrier_Increment64(volatile SbAtomic64* ptr, SbAtomic64 increment) { for (;;) { // Atomic exchange the old value with an incremented one. @@ -163,50 +164,51 @@ } } -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicAcquire_CompareAndSwap64(volatile SbAtomic64* ptr, SbAtomic64 old_value, SbAtomic64 new_value) { return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value); } -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicRelease_CompareAndSwap64(volatile SbAtomic64* ptr, SbAtomic64 old_value, SbAtomic64 new_value) { return SbAtomicNoBarrier_CompareAndSwap64(ptr, old_value, new_value); } -SB_C_FORCE_INLINE void SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr, - SbAtomic64 value) { +static SB_C_FORCE_INLINE void +SbAtomicNoBarrier_Store64(volatile SbAtomic64* ptr, + SbAtomic64 value) { *ptr = value; } -SB_C_FORCE_INLINE void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr, - SbAtomic64 value) { +static SB_C_FORCE_INLINE void SbAtomicAcquire_Store64(volatile SbAtomic64* ptr, + SbAtomic64 value) { *ptr = value; SbAtomicMemoryBarrier(); } -SB_C_FORCE_INLINE void SbAtomicRelease_Store64(volatile SbAtomic64* ptr, - SbAtomic64 value) { +static SB_C_FORCE_INLINE void SbAtomicRelease_Store64(volatile SbAtomic64* ptr, + SbAtomic64 value) { SbAtomicMemoryBarrier(); *ptr = value; } -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicNoBarrier_Load64(volatile const SbAtomic64* ptr) { return *ptr; } -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicAcquire_Load64(volatile const SbAtomic64* ptr) { SbAtomic64 value = *ptr; SbAtomicMemoryBarrier(); return value; } -SB_C_FORCE_INLINE SbAtomic64 +static SB_C_FORCE_INLINE SbAtomic64 SbAtomicRelease_Load64(volatile const SbAtomic64* ptr) { SbAtomicMemoryBarrier(); return *ptr;
diff --git a/src/starboard/shared/posix/log_raw.cc b/src/starboard/shared/posix/log_raw.cc index 10cfff0..f790d78 100644 --- a/src/starboard/shared/posix/log_raw.cc +++ b/src/starboard/shared/posix/log_raw.cc
@@ -23,14 +23,13 @@ // Cribbed from base/logging.cc's RawLog() function. size_t bytes_written = 0; const size_t message_len = strlen(message); - int rv; while (bytes_written < message_len) { - rv = HANDLE_EINTR(write(STDERR_FILENO, message + bytes_written, - message_len - bytes_written)); - if (rv < 0) { + int retval = HANDLE_EINTR(write(STDERR_FILENO, message + bytes_written, + message_len - bytes_written)); + if (retval < 0) { // Give up, nothing we can do now. break; } - bytes_written += rv; + bytes_written += retval; } }
diff --git a/src/starboard/shared/posix/socket_receive_from.cc b/src/starboard/shared/posix/socket_receive_from.cc index 6acea4c..ca0cb04 100644 --- a/src/starboard/shared/posix/socket_receive_from.cc +++ b/src/starboard/shared/posix/socket_receive_from.cc
@@ -27,6 +27,8 @@ char* out_data, int data_size, SbSocketAddress* out_source) { + const int kRecvFlags = 0; + if (!SbSocketIsValid(socket)) { errno = EBADF; return -1; @@ -52,7 +54,8 @@ } } - ssize_t bytes_read = recv(socket->socket_fd, out_data, data_size, 0); + ssize_t bytes_read = + recv(socket->socket_fd, out_data, data_size, kRecvFlags); if (bytes_read >= 0) { socket->error = kSbSocketOk; return static_cast<int>(bytes_read); @@ -66,8 +69,9 @@ return -1; } else if (socket->protocol == kSbSocketProtocolUdp) { sbposix::SockAddr sock_addr; - ssize_t bytes_read = recvfrom(socket->socket_fd, out_data, data_size, 0, - sock_addr.sockaddr(), &sock_addr.length); + ssize_t bytes_read = + recvfrom(socket->socket_fd, out_data, data_size, kRecvFlags, + sock_addr.sockaddr(), &sock_addr.length); if (bytes_read >= 0) { if (out_source) {
diff --git a/src/starboard/shared/posix/socket_send_to.cc b/src/starboard/shared/posix/socket_send_to.cc index 23e134d..6cd0c70 100644 --- a/src/starboard/shared/posix/socket_send_to.cc +++ b/src/starboard/shared/posix/socket_send_to.cc
@@ -26,6 +26,11 @@ const char* data, int data_size, const SbSocketAddress* destination) { +#if defined(MSG_NOSIGNAL) + const int kSendFlags = MSG_NOSIGNAL; +#else + const int kSendFlags = 0; +#endif if (!SbSocketIsValid(socket)) { errno = EBADF; return -1; @@ -39,7 +44,8 @@ return -1; } - ssize_t bytes_written = send(socket->socket_fd, data, data_size, 0); + ssize_t bytes_written = + send(socket->socket_fd, data, data_size, kSendFlags); if (bytes_written >= 0) { socket->error = kSbSocketOk; return static_cast<int>(bytes_written); @@ -70,8 +76,8 @@ sockaddr_length = sock_addr.length; } - ssize_t bytes_written = sendto(socket->socket_fd, data, data_size, 0, - sockaddr, sockaddr_length); + ssize_t bytes_written = sendto(socket->socket_fd, data, data_size, + kSendFlags, sockaddr, sockaddr_length); if (bytes_written >= 0) { socket->error = kSbSocketOk; return static_cast<int>(bytes_written);
diff --git a/src/starboard/shared/posix/time_get_monotonic_thread_now.cc b/src/starboard/shared/posix/time_get_monotonic_thread_now.cc new file mode 100644 index 0000000..7050050 --- /dev/null +++ b/src/starboard/shared/posix/time_get_monotonic_thread_now.cc
@@ -0,0 +1,30 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/time.h" + +#include <time.h> + +#include "starboard/log.h" +#include "starboard/shared/posix/time_internal.h" + +SbTimeMonotonic SbTimeGetMonotonicThreadNow() { + struct timespec time; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time) != 0) { + SB_NOTREACHED() << "clock_gettime(CLOCK_THREAD_CPUTIME_ID) failed."; + return 0; + } + + return FromTimespecDelta(&time); +}
diff --git a/src/starboard/shared/pthread/thread_create.cc b/src/starboard/shared/pthread/thread_create.cc index 70a1fdb..6cc9028 100644 --- a/src/starboard/shared/pthread/thread_create.cc +++ b/src/starboard/shared/pthread/thread_create.cc
@@ -1,4 +1,4 @@ -// Copyright 2015 Google Inc. All Rights Reserved. +// Copyright 2016 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,10 +16,28 @@ #include <pthread.h> #include <sched.h> +#include <sys/resource.h> +#include <unistd.h> +#include "starboard/log.h" #include "starboard/shared/pthread/is_success.h" +#include "starboard/shared/pthread/thread_create_priority.h" #include "starboard/string.h" +namespace starboard { +namespace shared { +namespace pthread { + +#if !SB_HAS(THREAD_PRIORITY_SUPPORT) +// Default implementation without thread priority support +void ThreadSetPriority(SbThreadPriority /* priority */) { +} +#endif + +} // namespace pthread +} // namespace shared +} // namespace starboard + namespace { struct ThreadParams { @@ -27,6 +45,7 @@ SbThreadEntryPoint entry_point; char name[128]; void* context; + SbThreadPriority priority; }; void* ThreadFunc(void* context) { @@ -38,6 +57,8 @@ SbThreadSetName(thread_params->name); } + starboard::shared::pthread::ThreadSetPriority(thread_params->priority); + delete thread_params; if (SbThreadIsValidAffinity(affinity)) { @@ -84,10 +105,6 @@ pthread_attr_setstacksize(&attributes, stack_size); } - // Here is where we would use priority, but it doesn't really work on Linux - // without using a realtime scheduling policy, according to this article: - // http://stackoverflow.com/questions/3649281/how-to-increase-thread-priority-in-pthreads/3663250 - ThreadParams* params = new ThreadParams(); params->affinity = affinity; params->entry_point = entry_point; @@ -99,6 +116,8 @@ params->name[0] = '\0'; } + params->priority = priority; + SbThread thread = kSbThreadInvalid; result = pthread_create(&thread, &attributes, ThreadFunc, params);
diff --git a/src/starboard/shared/pthread/thread_create_priority.h b/src/starboard/shared/pthread/thread_create_priority.h new file mode 100644 index 0000000..1c67ef5 --- /dev/null +++ b/src/starboard/shared/pthread/thread_create_priority.h
@@ -0,0 +1,34 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_PTHREAD_THREAD_CREATE_PRIORITY_H_ +#define STARBOARD_SHARED_PTHREAD_THREAD_CREATE_PRIORITY_H_ + +#include "starboard/thread.h" + +namespace starboard { +namespace shared { +namespace pthread { + +// Set priority of the current thread. +// +// Implement this in a platform-specific thread_create_priority.cc if the +// platform SB_HAS(THREAD_PRIORITY_SUPPORT) +void ThreadSetPriority(SbThreadPriority priority); + +} // namespace pthread +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_PTHREAD_THREAD_CREATE_PRIORITY_H_
diff --git a/src/starboard/shared/pthread/types_public.h b/src/starboard/shared/pthread/types_public.h index 5475df8..59ee65e 100644 --- a/src/starboard/shared/pthread/types_public.h +++ b/src/starboard/shared/pthread/types_public.h
@@ -52,6 +52,6 @@ typedef pthread_t SbThread; // Well-defined constant value to mean "no thread handle." -#define kSbThreadInvalid (SbThread) - 1 +#define kSbThreadInvalid (SbThread)-1 #endif // STARBOARD_SHARED_PTHREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/shared/signal/crash_signals.cc b/src/starboard/shared/signal/crash_signals.cc index 0d74011..78b3ab2 100644 --- a/src/starboard/shared/signal/crash_signals.cc +++ b/src/starboard/shared/signal/crash_signals.cc
@@ -32,7 +32,7 @@ }; const int kStopSignalsToTrap[] = { - SIGTERM, SIGINT, SIGHUP, + SIGHUP, }; void Crash(int signal_id) {
diff --git a/src/starboard/shared/signal/crash_signals_sigaction.cc b/src/starboard/shared/signal/crash_signals_sigaction.cc index 5273ab1..da318a8 100644 --- a/src/starboard/shared/signal/crash_signals_sigaction.cc +++ b/src/starboard/shared/signal/crash_signals_sigaction.cc
@@ -32,7 +32,7 @@ }; const int kStopSignalsToTrap[] = { - SIGTERM, SIGINT, SIGHUP, + SIGHUP, }; void SetSignalHandler(int signal_id, SignalHandlerFunction handler) {
diff --git a/src/starboard/shared/signal/suspend_signals.cc b/src/starboard/shared/signal/suspend_signals.cc index 6bb42d8..4048a3b 100644 --- a/src/starboard/shared/signal/suspend_signals.cc +++ b/src/starboard/shared/signal/suspend_signals.cc
@@ -15,6 +15,7 @@ #include "starboard/shared/signal/suspend_signals.h" #include <signal.h> +#include <sys/socket.h> #include "starboard/configuration.h" #include "starboard/log.h" @@ -71,19 +72,34 @@ } // namespace +#if !defined(MSG_NOSIGNAL) && defined(SO_NOSIGPIPE) +// See "#if !defined(MSG_NOSIGNAL)" below. +// OS X, which we do not build for today, has another mechanism which +// should be used. +#error On this platform, please use SO_NOSIGPIPE and leave the SIGPIPE \ + handler at default. +#endif + void InstallSuspendSignalHandlers() { SetSignalHandler(SIGTSTP, &Suspend); UnblockSignal(SIGTSTP); SetSignalHandler(SIGCONT, &Resume); - // We might receive SIGPIPE after resuming. We should not terminate. +#if !defined(MSG_NOSIGNAL) + // By default in POSIX, sending to a closed socket causes a SIGPIPE + // If we cannot disable that behavior, we must ignore SIGPIPE. + // Ignoring SIGPIPE means cases that use pipes to redirect the stdio + // log messages may behave in surprising ways, so it's not desirable. SetSignalHandler(SIGPIPE, &Ignore); +#endif } void UninstallSuspendSignalHandlers() { SetSignalHandler(SIGCONT, SIG_DFL); SetSignalHandler(SIGTSTP, SIG_DFL); +#if !defined(MSG_NOSIGNAL) SetSignalHandler(SIGPIPE, SIG_DFL); +#endif } } // namespace signal
diff --git a/src/starboard/shared/starboard/application.cc b/src/starboard/shared/starboard/application.cc index 26e08db..4f2e850 100644 --- a/src/starboard/shared/starboard/application.cc +++ b/src/starboard/shared/starboard/application.cc
@@ -17,7 +17,6 @@ #include "starboard/atomic.h" #include "starboard/condition_variable.h" #include "starboard/event.h" -#include "starboard/log.h" #include "starboard/memory.h" #include "starboard/string.h" @@ -77,7 +76,7 @@ reinterpret_cast<SbAtomicPtr>(NULL))); SB_DCHECK(old_instance); SB_DCHECK(old_instance == this); - SbMemoryFree(start_link_); + SbMemoryDeallocate(start_link_); } int Application::Run(int argc, char** argv) { @@ -131,7 +130,7 @@ #if SB_HAS(PLAYER) && SB_IS(PLAYER_PUNCHED_OUT) void Application::HandleFrame(SbPlayer player, - const player::VideoFrame& frame, + const scoped_refptr<VideoFrame>& frame, int x, int y, int width, @@ -141,7 +140,7 @@ #endif // SB_HAS(PLAYER) && SB_IS(PLAYER_PUNCHED_OUT) void Application::SetStartLink(const char* start_link) { - SbMemoryFree(start_link_); + SbMemoryDeallocate(start_link_); if (start_link) { start_link_ = SbStringDuplicate(start_link); } else {
diff --git a/src/starboard/shared/starboard/application.h b/src/starboard/shared/starboard/application.h index 7328f2e..4387bed 100644 --- a/src/starboard/shared/starboard/application.h +++ b/src/starboard/shared/starboard/application.h
@@ -38,6 +38,8 @@ // dispatching events to the Starboard event handler, SbEventHandle. class Application { public: + typedef player::VideoFrame VideoFrame; + // You can use a void(void *) function to signal that a state-transition event // has completed. typedef SbEventDataDestructor EventHandledCallback; @@ -200,7 +202,7 @@ // used when the application needs to composite video frames with punch-out // video manually (should be rare). Will be called from an external thread. void HandleFrame(SbPlayer player, - const player::VideoFrame& frame, + const scoped_refptr<VideoFrame>& frame, int x, int y, int width, @@ -222,7 +224,7 @@ // Subclasses may override this method to accept video frames from the media // system. Will be called from an external thread. virtual void AcceptFrame(SbPlayer player, - const player::VideoFrame& frame, + const scoped_refptr<VideoFrame>& frame, int x, int y, int width,
diff --git a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc index 09b2402..cd88dd8 100644 --- a/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc +++ b/src/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc
@@ -106,7 +106,7 @@ &is_playing, &is_eos_reached, context_); if (is_playing) { int frames_to_consume = - std::min(kMaxFramesToConsumePerRequest, frames_in_buffer / 2); + std::min(kMaxFramesToConsumePerRequest, frames_in_buffer); SbThreadSleep(frames_to_consume * kSbTimeSecond / sampling_frequency_hz_); consume_frame_func_(frames_to_consume, context_);
diff --git a/src/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc b/src/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc index 3317806..8648663 100644 --- a/src/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc +++ b/src/starboard/shared/starboard/blitter_blit_rect_to_rect_tiled.cc
@@ -248,9 +248,11 @@ } } - return SbBlitterBlitRectsToRects(context, source_surface, src_rects, - dst_rects, num_tiles); + bool result = SbBlitterBlitRectsToRects(context, source_surface, src_rects, + dst_rects, num_tiles); delete[] src_rects; delete[] dst_rects; + + return result; }
diff --git a/src/starboard/shared/starboard/file_storage/storage_internal.h b/src/starboard/shared/starboard/file_storage/storage_internal.h index ea322b8..6bb3362 100644 --- a/src/starboard/shared/starboard/file_storage/storage_internal.h +++ b/src/starboard/shared/starboard/file_storage/storage_internal.h
@@ -33,9 +33,9 @@ namespace shared { namespace starboard { // Gets the path to the storage file for the given user. -SB_C_INLINE bool GetUserStorageFilePath(SbUser user, - char* out_path, - int path_size) { +static SB_C_INLINE bool GetUserStorageFilePath(SbUser user, + char* out_path, + int path_size) { bool success = SbUserGetProperty(user, kSbUserPropertyHomeDirectory, out_path, path_size); if (!success) {
diff --git a/src/starboard/shared/starboard/localized_strings.cc b/src/starboard/shared/starboard/localized_strings.cc index 2639c53..0f802e6 100644 --- a/src/starboard/shared/starboard/localized_strings.cc +++ b/src/starboard/shared/starboard/localized_strings.cc
@@ -116,7 +116,7 @@ return false; } SB_DCHECK(file_contents.length() > 0); - SB_DCHECK(file_contents.back() == '\n'); + SB_DCHECK(file_contents[file_contents.length() - 1] == '\n'); // Each line of the file corresponds to one message (key/value). size_t pos = 0;
diff --git a/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc b/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc index 9bfd9a7..0f417fd 100644 --- a/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc +++ b/src/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc
@@ -16,10 +16,10 @@ #include "starboard/character.h" #include "starboard/log.h" -#include "starboard/shared/starboard/media/mime_parser.h" +#include "starboard/shared/starboard/media/mime_type.h" #include "starboard/string.h" -using starboard::shared::starboard::media::MimeParser; +using starboard::shared::starboard::media::MimeType; SbMediaSupportType SbMediaCanPlayMimeAndKeySystem(const char* mime, const char* key_system) { @@ -37,27 +37,32 @@ return kSbMediaSupportTypeNotSupported; } } - MimeParser parser(mime); - if (!parser.is_valid()) { + MimeType mime_type(mime); + if (!mime_type.is_valid()) { SB_DLOG(WARNING) << mime << " is not a valid mime type"; return kSbMediaSupportTypeNotSupported; } - if (parser.mime() == "application/octet-stream") { + int codecs_index = mime_type.GetParamIndexByName("codecs"); + if (codecs_index != MimeType::kInvalidParamIndex && codecs_index != 0) { + SB_DLOG(WARNING) << mime << " is not a valid mime type"; + return kSbMediaSupportTypeNotSupported; + } + if (mime_type.type() == "application" && + mime_type.subtype() == "octet-stream") { return kSbMediaSupportTypeProbably; } - if (parser.mime() == "audio/mp4") { + if (mime_type.type() == "audio" && mime_type.subtype() == "mp4") { return kSbMediaSupportTypeProbably; } - if (parser.mime() == "video/mp4") { + if (mime_type.type() == "video" && mime_type.subtype() == "mp4") { return kSbMediaSupportTypeProbably; } #if SB_HAS(MEDIA_WEBM_VP9_SUPPORT) - if (parser.mime() == "video/webm") { - if (!parser.HasParam("codecs")) { + if (mime_type.type() == "video" && mime_type.subtype() == "webm") { + if (codecs_index == MimeType::kInvalidParamIndex) { return kSbMediaSupportTypeMaybe; } - std::string codecs = parser.GetParam("codecs"); - if (codecs == "vp9") { + if (mime_type.GetParamStringValue(0) == "vp9") { return kSbMediaSupportTypeProbably; } return kSbMediaSupportTypeNotSupported;
diff --git a/src/starboard/shared/starboard/media/mime_parser.cc b/src/starboard/shared/starboard/media/mime_parser.cc deleted file mode 100644 index 7db734a..0000000 --- a/src/starboard/shared/starboard/media/mime_parser.cc +++ /dev/null
@@ -1,141 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/shared/starboard/media/mime_parser.h" - -#include <vector> - -#include "starboard/character.h" -#include "starboard/log.h" -#include "starboard/string.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace media { - -namespace { - -void ToLower(std::string* string) { - SB_DCHECK(string); - for (size_t i = 0; i < string->size(); ++i) { - (*string)[i] = SbCharacterToLower((*string)[i]); - } -} - -void Trim(std::string* string) { - SB_DCHECK(string); - while (!string->empty() && SbCharacterIsSpace(*string->rbegin())) { - string->resize(string->size() - 1); - } - while (!string->empty() && SbCharacterIsSpace(*string->begin())) { - string->erase(string->begin()); - } -} - -bool IsSeparator(char ch, std::string separator) { - if (separator.empty()) { - return SbCharacterIsSpace(ch); - } - return separator.find(ch) != separator.npos; -} - -// For any given string, split it into substrings separated by any character in -// the |separator| string. If |separator| is empty string, treat any white -// space as separators. -std::vector<std::string> SplitString(std::string string, - std::string separator = "") { - std::vector<std::string> result; - while (!string.empty()) { - Trim(&string); - if (string.empty()) { - break; - } - bool added = false; - for (size_t i = 0; i < string.size(); ++i) { - if (IsSeparator(string[i], separator)) { - result.push_back(string.substr(0, i)); - Trim(&result.back()); - string = string.substr(i + 1); - added = true; - break; - } - } - if (!added) { - Trim(&string); - result.push_back(string); - break; - } - } - return result; -} - -} // namespace - -MimeParser::MimeParser(std::string mime_with_params) { - Trim(&mime_with_params); - ToLower(&mime_with_params); - - std::vector<std::string> splits = SplitString(mime_with_params, ";"); - if (splits.empty()) { - return; // It is invalid, leave |mime_| as empty. - } - mime_ = splits[0]; - // Mime is in the form of "video/mp4". - if (SplitString(mime_, "/").size() != 2) { - mime_.clear(); - return; - } - splits.erase(splits.begin()); - - while (!splits.empty()) { - std::string params = *splits.begin(); - splits.erase(splits.begin()); - - // Param is in the form of 'name=value' or 'name="value"'. - std::vector<std::string> name_and_value = SplitString(params, "="); - if (name_and_value.size() != 2) { - mime_.clear(); - return; - } - std::string name = name_and_value[0]; - std::string value = name_and_value[1]; - - if (value.size() >= 2 && value[0] == '\"' && - value[value.size() - 1] == '\"') { - value.erase(value.begin()); - value.resize(value.size() - 1); - Trim(&value); - } - - params_[name] = value; - } -} - -bool MimeParser::HasParam(const std::string& name) const { - return params_.find(name) != params_.end(); -} - -std::string MimeParser::GetParam(const std::string& name) const { - std::map<std::string, std::string>::const_iterator iter = params_.find(name); - if (iter == params_.end()) { - return ""; - } - return iter->second; -} - -} // namespace media -} // namespace starboard -} // namespace shared -} // namespace starboard
diff --git a/src/starboard/shared/starboard/media/mime_parser.h b/src/starboard/shared/starboard/media/mime_parser.h deleted file mode 100644 index b896b2d..0000000 --- a/src/starboard/shared/starboard/media/mime_parser.h +++ /dev/null
@@ -1,54 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_PARSER_H_ -#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_PARSER_H_ - -#include <map> -#include <string> - -#include "starboard/shared/internal_only.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace media { - -// This class can be used to parse media mime types with params. For example, -// the following mime type 'video/mp4; codecs="avc1.4d401e"; width=640' will be -// parsed into: -// mime => video/mp4 -// param: codecs => avc1.4d401e -// param: width => 640 -class MimeParser { - public: - explicit MimeParser(std::string mime_with_params); - - bool is_valid() const { return !mime_.empty(); } - const std::string& mime() const { return mime_; } - - bool HasParam(const std::string& name) const; - std::string GetParam(const std::string& name) const; - - private: - std::string mime_; - std::map<std::string, std::string> params_; -}; - -} // namespace media -} // namespace starboard -} // namespace shared -} // namespace starboard - -#endif // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_PARSER_H_
diff --git a/src/starboard/shared/starboard/media/mime_type.cc b/src/starboard/shared/starboard/media/mime_type.cc new file mode 100644 index 0000000..d77b2d5 --- /dev/null +++ b/src/starboard/shared/starboard/media/mime_type.cc
@@ -0,0 +1,217 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/media/mime_type.h" + +#include "starboard/character.h" +#include "starboard/log.h" +#include "starboard/string.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +namespace { + +typedef std::vector<std::string> Strings; + +MimeType::ParamType GetParamTypeByValue(const std::string& value) { + int count; + int i; + if (SbStringScanF(value.c_str(), "%d%n", &i, &count) == 1 && + count == value.size()) { + return MimeType::kParamTypeInteger; + } + float f; + if (SbStringScanF(value.c_str(), "%g%n", &f, &count) == 1 && + count == value.size()) { + return MimeType::kParamTypeFloat; + } + return MimeType::kParamTypeString; +} + +bool ContainsSpace(const std::string& str) { + for (size_t i = 0; i < str.size(); ++i) { + if (SbCharacterIsSpace(str[i])) { + return true; + } + } + + return false; +} + +void Trim(std::string* str) { + while (!str->empty() && SbCharacterIsSpace(*str->begin())) { + str->erase(str->begin()); + } + while (!str->empty() && SbCharacterIsSpace(*str->rbegin())) { + str->resize(str->size() - 1); + } +} + +Strings SplitAndTrim(const std::string& str, char ch) { + Strings result; + size_t pos = 0; + + for (;;) { + size_t next = str.find(ch, pos); + result.push_back(str.substr(pos, next - pos)); + Trim(&result.back()); + if (next == str.npos) { + break; + } + pos = next + 1; + } + + return result; +} + +} // namespace + +const int MimeType::kInvalidParamIndex = -1; + +MimeType::MimeType(const std::string& content_type) : is_valid_(false) { + Strings components = SplitAndTrim(content_type, ';'); + + SB_DCHECK(!components.empty()); + + // 1. Verify if there is a valid type/subtype in the very beginning. + if (ContainsSpace(components.front())) { + return; + } + + std::vector<std::string> type_and_container = + SplitAndTrim(components.front(), '/'); + if (type_and_container.size() != 2 || type_and_container[0].empty() || + type_and_container[1].empty()) { + return; + } + type_ = type_and_container[0]; + subtype_ = type_and_container[1]; + components.erase(components.begin()); + + // 2. Verify the parameters have valid formats, we want to be strict here. + for (Strings::iterator iter = components.begin(); iter != components.end(); + ++iter) { + std::vector<std::string> name_and_value = SplitAndTrim(*iter, '='); + if (name_and_value.size() != 2 || name_and_value[0].empty() || + name_and_value[1].empty()) { + return; + } + Param param; + if (name_and_value[1].size() > 2 && name_and_value[1][0] == '\"' && + *name_and_value[1].rbegin() == '\"') { + param.type = kParamTypeString; + param.value = name_and_value[1].substr(1, name_and_value[1].size() - 2); + } else { + param.type = GetParamTypeByValue(name_and_value[1]); + param.value = name_and_value[1]; + } + param.name = name_and_value[0]; + params_.push_back(param); + } + + is_valid_ = true; +} + +int MimeType::GetParamCount() const { + SB_DCHECK(is_valid()); + + return static_cast<int>(params_.size()); +} + +MimeType::ParamType MimeType::GetParamType(int index) const { + SB_DCHECK(is_valid()); + SB_DCHECK(index < GetParamCount()); + + return params_[index].type; +} + +const std::string& MimeType::GetParamName(int index) const { + SB_DCHECK(is_valid()); + SB_DCHECK(index < GetParamCount()); + + return params_[index].name; +} + +int MimeType::GetParamIntValue(int index) const { + SB_DCHECK(is_valid()); + SB_DCHECK(index < GetParamCount()); + SB_DCHECK(GetParamType(index) == kParamTypeInteger); + + int i; + SbStringScanF(params_[index].value.c_str(), "%d", &i); + return i; +} + +float MimeType::GetParamFloatValue(int index) const { + SB_DCHECK(is_valid()); + SB_DCHECK(index < GetParamCount()); + SB_DCHECK(GetParamType(index) == kParamTypeInteger || + GetParamType(index) == kParamTypeFloat); + + float f; + SbStringScanF(params_[index].value.c_str(), "%g", &f); + + return f; +} + +const std::string& MimeType::GetParamStringValue(int index) const { + SB_DCHECK(is_valid()); + SB_DCHECK(index < GetParamCount()); + + return params_[index].value; +} + +int MimeType::GetParamIntValue(const char* name, int default_value) const { + int index = GetParamIndexByName(name); + if (index != kInvalidParamIndex) { + return GetParamIntValue(index); + } + return default_value; +} + +float MimeType::GetParamFloatValue(const char* name, + float default_value) const { + int index = GetParamIndexByName(name); + if (index != kInvalidParamIndex) { + return GetParamFloatValue(index); + } + return default_value; +} + +const std::string& MimeType::GetParamStringValue( + const char* name, + const std::string& default_value) const { + int index = GetParamIndexByName(name); + if (index != kInvalidParamIndex) { + return GetParamStringValue(index); + } + return default_value; +} + +int MimeType::GetParamIndexByName(const char* name) const { + for (size_t i = 0; i < params_.size(); ++i) { + if (SbStringCompareNoCase(params_[i].name.c_str(), name) == 0) { + return static_cast<int>(i); + } + } + return kInvalidParamIndex; +} + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/src/starboard/shared/starboard/media/mime_type.h b/src/starboard/shared/starboard/media/mime_type.h new file mode 100644 index 0000000..eb5b2f2 --- /dev/null +++ b/src/starboard/shared/starboard/media/mime_type.h
@@ -0,0 +1,95 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_MEDIA_MIME_TYPE_H_ +#define STARBOARD_SHARED_STARBOARD_MEDIA_MIME_TYPE_H_ + +#include <string> +#include <vector> + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { + +// This class can be used to parse a content type for media in the form of +// "type/subtype; param1=value1; param2="value2". For example, the content type +// "video/webm; codecs="vp9"; width=1920; height=1080; framerate=59.96" will be +// parsed into: +// type: video +// subtype: webm +// codecs: vp9 +// width: 1920 +// height: 1080 +// framerate: 59.96 +// +// The following are the restrictions on the components: +// 1. Type/subtype has to be in the very beginning. +// 2. String values may be double quoted. +// 3. Numeric values cannot be double quoted. +class MimeType { + public: + enum ParamType { + kParamTypeInteger, + kParamTypeFloat, + kParamTypeString, + }; + + static const int kInvalidParamIndex; + + explicit MimeType(const std::string& content_type); + + bool is_valid() const { return is_valid_; } + + const std::string& type() const { return type_; } + const std::string& subtype() const { return subtype_; } + + int GetParamIndexByName(const char* name) const; + int GetParamCount() const; + ParamType GetParamType(int index) const; + const std::string& GetParamName(int index) const; + + int GetParamIntValue(int index) const; + float GetParamFloatValue(int index) const; + const std::string& GetParamStringValue(int index) const; + + int GetParamIntValue(const char* name, int default_value) const; + float GetParamFloatValue(const char* name, float default_value) const; + const std::string& GetParamStringValue( + const char* name, + const std::string& default_value) const; + + private: + struct Param { + ParamType type; + std::string name; + std::string value; + }; + + // Use std::vector as the number of components are usually small and we'd like + // to keep the order of components. + typedef std::vector<Param> Params; + + bool is_valid_; + std::string type_; + std::string subtype_; + Params params_; +}; + +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_MEDIA_MIME_TYPE_H_
diff --git a/src/starboard/shared/starboard/media/mime_type_test.cc b/src/starboard/shared/starboard/media/mime_type_test.cc new file mode 100644 index 0000000..44d0371 --- /dev/null +++ b/src/starboard/shared/starboard/media/mime_type_test.cc
@@ -0,0 +1,269 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/media/mime_type.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace media { +namespace { + +TEST(MimeTypeTest, EmptyString) { + MimeType mime_type(""); + EXPECT_FALSE(mime_type.is_valid()); +} + +// Valid mime type must have a type/subtype without space in between. +TEST(MimeTypeTest, InvalidType) { + { + MimeType mime_type("video"); + EXPECT_FALSE(mime_type.is_valid()); + } + + { + MimeType mime_type("video/"); + EXPECT_FALSE(mime_type.is_valid()); + } + + { + MimeType mime_type("/mp4"); + EXPECT_FALSE(mime_type.is_valid()); + } + + { + MimeType mime_type("video /mp4"); + EXPECT_FALSE(mime_type.is_valid()); + } + + { + MimeType mime_type("video/ mp4"); + EXPECT_FALSE(mime_type.is_valid()); + } + + { + MimeType mime_type("video / mp4"); + EXPECT_FALSE(mime_type.is_valid()); + } +} + +TEST(MimeTypeTest, ValidContentTypeWithTypeAndSubtypeOnly) { + { + MimeType mime_type("video/mp4"); + EXPECT_TRUE(mime_type.is_valid()); + EXPECT_EQ("video", mime_type.type()); + EXPECT_EQ("mp4", mime_type.subtype()); + } + + { + MimeType mime_type("audio/mp4"); + EXPECT_TRUE(mime_type.is_valid()); + } + + { + MimeType mime_type("abc/xyz"); + EXPECT_TRUE(mime_type.is_valid()); + } +} + +TEST(MimeTypeTest, TypeNotAtBeginning) { + { + MimeType mime_type(";video/mp4"); + EXPECT_FALSE(mime_type.is_valid()); + } + + { + MimeType mime_type("codecs=\"abc\"; audio/mp4"); + EXPECT_FALSE(mime_type.is_valid()); + } +} + +TEST(MimeTypeTest, EmptyComponent) { + { + MimeType mime_type("video/mp4;"); + EXPECT_FALSE(mime_type.is_valid()); + } + + { + MimeType mime_type("video/mp4;;"); + EXPECT_FALSE(mime_type.is_valid()); + } + + { + MimeType mime_type("audio/mp4; codecs=\"abc\";"); + EXPECT_FALSE(mime_type.is_valid()); + } +} + +TEST(MimeTypeTest, ValidContentTypeWithParams) { + { + MimeType mime_type("video/mp4; name=123"); + EXPECT_TRUE(mime_type.is_valid()); + } + + { + MimeType mime_type("audio/mp4;codecs=\"abc\""); + EXPECT_TRUE(mime_type.is_valid()); + } + + { + MimeType mime_type(" audio/mp4 ; codecs = \"abc\" "); + EXPECT_TRUE(mime_type.is_valid()); + } +} + +TEST(MimeTypeTest, GetParamIndexByName) { + MimeType mime_type("video/mp4; name=123"); + EXPECT_EQ(MimeType::kInvalidParamIndex, + mime_type.GetParamIndexByName("video")); + EXPECT_EQ(MimeType::kInvalidParamIndex, mime_type.GetParamIndexByName("mp4")); + EXPECT_EQ(0, mime_type.GetParamIndexByName("name")); +} + +TEST(MimeTypeTest, ParamCount) { + { + MimeType mime_type("video/mp4"); + EXPECT_EQ(0, mime_type.GetParamCount()); + } + + { + MimeType mime_type("video/mp4; width=1920"); + EXPECT_EQ(1, mime_type.GetParamCount()); + } + + { + MimeType mime_type("video/mp4; width=1920; height=1080"); + EXPECT_EQ(2, mime_type.GetParamCount()); + } +} + +TEST(MimeTypeTest, GetParamType) { + { + MimeType mime_type("video/mp4; name0=123; name1=1.2; name2=xyz"); + EXPECT_EQ(MimeType::kParamTypeInteger, mime_type.GetParamType(0)); + EXPECT_EQ(MimeType::kParamTypeFloat, mime_type.GetParamType(1)); + EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(2)); + } + + { + MimeType mime_type("video/mp4; name0=\"123\"; name1=\"abc\""); + EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(0)); + EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(1)); + } + + { + MimeType mime_type("video/mp4; name1=\" abc \""); + EXPECT_EQ(MimeType::kParamTypeString, mime_type.GetParamType(0)); + } +} + +TEST(MimeTypeTest, GetParamName) { + { + MimeType mime_type("video/mp4; name0=123; name1=1.2; name2=xyz"); + EXPECT_EQ("name0", mime_type.GetParamName(0)); + EXPECT_EQ("name1", mime_type.GetParamName(1)); + EXPECT_EQ("name2", mime_type.GetParamName(2)); + } +} + +TEST(MimeTypeTest, GetParamIntValueWithIndex) { + { + MimeType mime_type("video/mp4; name=123"); + EXPECT_EQ(123, mime_type.GetParamIntValue(0)); + } + + { + MimeType mime_type("video/mp4; width=1920; height=1080"); + EXPECT_EQ(1920, mime_type.GetParamIntValue(0)); + EXPECT_EQ(1080, mime_type.GetParamIntValue(1)); + } + + { + MimeType mime_type("audio/mp4; channels=6"); + EXPECT_EQ(6, mime_type.GetParamIntValue(0)); + } +} + +TEST(MimeTypeTest, GetParamFloatValueWithIndex) { + { + MimeType mime_type("video/mp4; name0=123; name1=123.4"); + EXPECT_FLOAT_EQ(123., mime_type.GetParamFloatValue(0)); + EXPECT_FLOAT_EQ(123.4, mime_type.GetParamFloatValue(1)); + } +} + +TEST(MimeTypeTest, GetParamStringValueWithIndex) { + { + MimeType mime_type("video/mp4; name0=123; name1=abc; name2=\"xyz\""); + EXPECT_EQ("123", mime_type.GetParamStringValue(0)); + EXPECT_EQ("abc", mime_type.GetParamStringValue(1)); + EXPECT_EQ("xyz", mime_type.GetParamStringValue(2)); + } + + { + MimeType mime_type("video/mp4; name=\" xyz \""); + EXPECT_EQ(" xyz ", mime_type.GetParamStringValue(0)); + } +} + +TEST(MimeTypeTest, GetParamIntValueWithName) { + { + MimeType mime_type("video/mp4; name=123"); + EXPECT_EQ(123, mime_type.GetParamIntValue("name", 0)); + EXPECT_EQ(6, mime_type.GetParamIntValue("channels", 6)); + } + + { + MimeType mime_type("video/mp4; width=1920; height=1080"); + EXPECT_EQ(1920, mime_type.GetParamIntValue("width", 0)); + EXPECT_EQ(1080, mime_type.GetParamIntValue("height", 0)); + } + + { + MimeType mime_type("audio/mp4; channels=6"); + EXPECT_EQ(6, mime_type.GetParamIntValue("channels", 0)); + } +} + +TEST(MimeTypeTest, GetParamFloatValueWithName) { + { + MimeType mime_type("video/mp4; name0=123; name1=123.4"); + EXPECT_FLOAT_EQ(123.f, mime_type.GetParamFloatValue("name0", 0.f)); + EXPECT_FLOAT_EQ(123.4f, mime_type.GetParamFloatValue("name1", 0.f)); + EXPECT_FLOAT_EQ(59.96f, mime_type.GetParamFloatValue("framerate", 59.96f)); + } +} + +TEST(MimeTypeTest, GetParamStringValueWithName) { + { + MimeType mime_type("video/mp4; name0=123; name1=abc; name2=\"xyz\""); + EXPECT_EQ("123", mime_type.GetParamStringValue("name0", "")); + EXPECT_EQ("abc", mime_type.GetParamStringValue("name1", "")); + EXPECT_EQ("xyz", mime_type.GetParamStringValue("name2", "")); + EXPECT_EQ("h263", mime_type.GetParamStringValue("codecs", "h263")); + } + + { + MimeType mime_type("video/mp4; name=\" xyz \""); + EXPECT_EQ(" xyz ", mime_type.GetParamStringValue("name", "")); + } +} + +} // namespace +} // namespace media +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/src/starboard/shared/starboard/memory_reporter_internal.h b/src/starboard/shared/starboard/memory_reporter_internal.h new file mode 100644 index 0000000..72b2bef --- /dev/null +++ b/src/starboard/shared/starboard/memory_reporter_internal.h
@@ -0,0 +1,40 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_MEMORY_REPORTER_INTERNAL_H_ +#define STARBOARD_SHARED_STARBOARD_MEMORY_REPORTER_INTERNAL_H_ + +#include "starboard/export.h" +#include "starboard/shared/internal_only.h" +#include "starboard/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Internal function used to track mapped memory. This is used internally by +// implementations of SbMemoryMap(). +SB_EXPORT void SbMemoryReporterReportMappedMemory(const void* memory, + size_t size); + +// Internal function used to track mapped memory. This is used internally by +// implementations of SbMemoryUnmap(). +SB_EXPORT void SbMemoryReporterReportUnmappedMemory(const void* memory, + size_t size); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // STARBOARD_SHARED_STARBOARD_MEMORY_REPORTER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/new.cc b/src/starboard/shared/starboard/new.cc index bf4f97b..7e08ed0 100644 --- a/src/starboard/shared/starboard/new.cc +++ b/src/starboard/shared/starboard/new.cc
@@ -22,7 +22,7 @@ } void operator delete(void* pointer) { - SbMemoryFree(pointer); + SbMemoryDeallocate(pointer); } void* operator new[](size_t size) { @@ -30,5 +30,5 @@ } void operator delete[](void* pointer) { - SbMemoryFree(pointer); + SbMemoryDeallocate(pointer); }
diff --git a/src/starboard/shared/starboard/player/audio_decoder_internal.h b/src/starboard/shared/starboard/player/audio_decoder_internal.h deleted file mode 100644 index 000a22c..0000000 --- a/src/starboard/shared/starboard/player/audio_decoder_internal.h +++ /dev/null
@@ -1,61 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_DECODER_INTERNAL_H_ -#define STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_DECODER_INTERNAL_H_ - -#include <vector> - -#include "starboard/media.h" -#include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/player/input_buffer_internal.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace player { - -// This class decodes encoded audio stream into playable audio data. -class AudioDecoder { - public: - virtual ~AudioDecoder() {} - - // Decode the encoded audio data stored in |input_buffer| and store the - // result in |output|. - virtual void Decode(const InputBuffer& input_buffer, - std::vector<float>* output) = 0; - // Note that there won't be more input data unless Reset() is called. - virtual void WriteEndOfStream() = 0; - // Clear any cached buffer of the codec and reset the state of the codec. - // This function will be called during seek to ensure that the left over - // data from previous buffers are cleared. - virtual void Reset() = 0; - - // Return the sample rate of the incoming audio. This should be used by the - // audio renderer as the sample rate of the underlying audio stream can be - // different than the sample rate stored in the meta data. - virtual int GetSamplesPerSecond() = 0; - - // Individual implementation has to implement this function to create an - // audio decoder. - static AudioDecoder* Create(SbMediaAudioCodec audio_codec, - const SbMediaAudioHeader& audio_header); -}; - -} // namespace player -} // namespace starboard -} // namespace shared -} // namespace starboard - -#endif // STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_DECODER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/audio_renderer_internal.cc b/src/starboard/shared/starboard/player/audio_renderer_internal.cc deleted file mode 100644 index c761c09..0000000 --- a/src/starboard/shared/starboard/player/audio_renderer_internal.cc +++ /dev/null
@@ -1,246 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/shared/starboard/player/audio_renderer_internal.h" - -#include <algorithm> - -#include "starboard/memory.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace player { - -namespace { -// TODO: This should be retrieved from the decoder. -// TODO: Make it not dependent on the frame size of AAC and HE-AAC. -const int kMaxFramesPerAccessUnit = 1024 * 2; -} // namespace - -AudioRenderer::AudioRenderer(AudioDecoder* decoder, - const SbMediaAudioHeader& audio_header) - : channels_(audio_header.number_of_channels), - paused_(true), - seeking_(false), - seeking_to_pts_(0), - frame_buffer_(kMaxCachedFrames * audio_header.number_of_channels), - frames_in_buffer_(0), - offset_in_frames_(0), - frames_consumed_(0), - end_of_stream_reached_(false), - decoder_(decoder), - audio_sink_(kSbAudioSinkInvalid) { - SB_DCHECK(decoder_ != NULL); - frame_buffers_[0] = &frame_buffer_[0]; -} - -AudioRenderer::~AudioRenderer() { - if (audio_sink_ != kSbAudioSinkInvalid) { - SbAudioSinkDestroy(audio_sink_); - } - delete decoder_; -} - -void AudioRenderer::WriteSample(const InputBuffer& input_buffer) { - if (end_of_stream_reached_) { - SB_LOG(ERROR) << "Appending audio sample at " << input_buffer.pts() - << " after EOS reached."; - return; - } - - SbMediaTime input_pts = input_buffer.pts(); - std::vector<float> decoded_audio; - decoder_->Decode(input_buffer, &decoded_audio); - if (decoded_audio.empty()) { - SB_DLOG(ERROR) << "decoded_audio contains no frames."; - return; - } - - { - ScopedLock lock(mutex_); - if (seeking_) { - if (input_pts < seeking_to_pts_) { - return; - } - } - - AppendFrames(&decoded_audio[0], decoded_audio.size() / channels_); - - if (seeking_ && frame_buffer_.size() > kPrerollFrames * channels_) { - seeking_ = false; - } - } - - // Create the audio sink if it is the first incoming AU after seeking. - if (audio_sink_ == kSbAudioSinkInvalid) { - int sample_rate = decoder_->GetSamplesPerSecond(); - // TODO: Implement resampler. - SB_DCHECK(sample_rate == - SbAudioSinkGetNearestSupportedSampleFrequency(sample_rate)); - // TODO: Handle sink creation failure. - audio_sink_ = SbAudioSinkCreate( - channels_, sample_rate, kSbMediaAudioSampleTypeFloat32, - kSbMediaAudioFrameStorageTypeInterleaved, - reinterpret_cast<SbAudioSinkFrameBuffers>(frame_buffers_), - kMaxCachedFrames, &AudioRenderer::UpdateSourceStatusFunc, - &AudioRenderer::ConsumeFramesFunc, this); - } -} - -void AudioRenderer::WriteEndOfStream() { - SB_LOG_IF(WARNING, end_of_stream_reached_) - << "Try to write EOS after EOS is reached"; - if (end_of_stream_reached_) { - return; - } - end_of_stream_reached_ = true; - decoder_->WriteEndOfStream(); - - ScopedLock lock(mutex_); - // If we are seeking, we consider the seek is finished if end of stream is - // reached as there won't be any audio data in future. - if (seeking_) { - seeking_ = false; - } -} - -void AudioRenderer::Play() { - ScopedLock lock(mutex_); - paused_ = false; -} - -void AudioRenderer::Pause() { - ScopedLock lock(mutex_); - paused_ = true; -} - -void AudioRenderer::Seek(SbMediaTime seek_to_pts) { - SB_DCHECK(seek_to_pts >= 0); - - SbAudioSinkDestroy(audio_sink_); - audio_sink_ = kSbAudioSinkInvalid; - - seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0); - seeking_ = true; - frames_in_buffer_ = 0; - offset_in_frames_ = 0; - frames_consumed_ = 0; - end_of_stream_reached_ = false; - - decoder_->Reset(); - return; -} - -bool AudioRenderer::IsEndOfStreamPlayed() const { - return end_of_stream_reached_ && frames_in_buffer_ == 0; -} - -bool AudioRenderer::CanAcceptMoreData() const { - ScopedLock lock(mutex_); - return frames_in_buffer_ <= kMaxCachedFrames - kMaxFramesPerAccessUnit && - !end_of_stream_reached_; -} - -bool AudioRenderer::IsSeekingInProgress() const { - return seeking_; -} - -SbMediaTime AudioRenderer::GetCurrentTime() { - if (seeking_) { - return seeking_to_pts_; - } - return seeking_to_pts_ + - frames_consumed_ * kSbMediaTimeSecond / - decoder_->GetSamplesPerSecond(); -} - -// static -void AudioRenderer::UpdateSourceStatusFunc(int* frames_in_buffer, - int* offset_in_frames, - bool* is_playing, - bool* is_eos_reached, - void* context) { - AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context); - SB_DCHECK(audio_renderer); - SB_DCHECK(frames_in_buffer); - SB_DCHECK(offset_in_frames); - SB_DCHECK(is_playing); - SB_DCHECK(is_eos_reached); - - audio_renderer->UpdateSourceStatus(frames_in_buffer, offset_in_frames, - is_playing, is_eos_reached); -} - -// static -void AudioRenderer::ConsumeFramesFunc(int frames_consumed, void* context) { - AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context); - SB_DCHECK(audio_renderer); - - audio_renderer->ConsumeFrames(frames_consumed); -} - -void AudioRenderer::UpdateSourceStatus(int* frames_in_buffer, - int* offset_in_frames, - bool* is_playing, - bool* is_eos_reached) { - ScopedLock lock(mutex_); - - *is_eos_reached = end_of_stream_reached_; - - if (paused_ || seeking_) { - *is_playing = false; - *frames_in_buffer = *offset_in_frames = 0; - return; - } - - *is_playing = true; - *frames_in_buffer = frames_in_buffer_; - *offset_in_frames = offset_in_frames_; -} - -void AudioRenderer::ConsumeFrames(int frames_consumed) { - ScopedLock lock(mutex_); - - SB_DCHECK(frames_consumed <= frames_in_buffer_); - offset_in_frames_ += frames_consumed; - offset_in_frames_ %= kMaxCachedFrames; - frames_in_buffer_ -= frames_consumed; - frames_consumed_ += frames_consumed; -} - -void AudioRenderer::AppendFrames(const float* source_buffer, - int frames_to_append) { - SB_DCHECK(frames_in_buffer_ + frames_to_append <= kMaxCachedFrames); - - int offset_to_append = - (offset_in_frames_ + frames_in_buffer_) % kMaxCachedFrames; - if (frames_to_append > kMaxCachedFrames - offset_to_append) { - SbMemoryCopy( - &frame_buffer_[offset_to_append * channels_], source_buffer, - (kMaxCachedFrames - offset_to_append) * sizeof(float) * channels_); - source_buffer += (kMaxCachedFrames - offset_to_append) * channels_; - frames_to_append -= kMaxCachedFrames - offset_to_append; - frames_in_buffer_ += kMaxCachedFrames - offset_to_append; - offset_to_append = 0; - } - SbMemoryCopy(&frame_buffer_[offset_to_append * channels_], source_buffer, - frames_to_append * sizeof(float) * channels_); - frames_in_buffer_ += frames_to_append; -} - -} // namespace player -} // namespace starboard -} // namespace shared -} // namespace starboard
diff --git a/src/starboard/shared/starboard/player/audio_renderer_internal.h b/src/starboard/shared/starboard/player/audio_renderer_internal.h deleted file mode 100644 index cbc68c8..0000000 --- a/src/starboard/shared/starboard/player/audio_renderer_internal.h +++ /dev/null
@@ -1,102 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_RENDERER_INTERNAL_H_ -#define STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_RENDERER_INTERNAL_H_ - -#include <vector> - -#include "starboard/audio_sink.h" -#include "starboard/log.h" -#include "starboard/media.h" -#include "starboard/mutex.h" -#include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/player/audio_decoder_internal.h" -#include "starboard/shared/starboard/player/input_buffer_internal.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace player { - -class AudioRenderer { - public: - AudioRenderer(AudioDecoder* decoder, const SbMediaAudioHeader& audio_header); - ~AudioRenderer(); - - bool is_valid() const { return true; } - - void WriteSample(const InputBuffer& input_buffer); - void WriteEndOfStream(); - - void Play(); - void Pause(); - void Seek(SbMediaTime seek_to_pts); - - bool IsEndOfStreamWritten() const { return end_of_stream_reached_; } - bool IsEndOfStreamPlayed() const; - bool CanAcceptMoreData() const; - bool IsSeekingInProgress() const; - SbMediaTime GetCurrentTime(); - - private: - // Preroll considered finished after either kPrerollFrames is cached or EOS - // is reached. - static const size_t kPrerollFrames = 64 * 1024; - // Set a soft limit for the max audio frames we can cache so we can: - // 1. Avoid using too much memory. - // 2. Have the audio cache full to simulate the state that the renderer can - // no longer accept more data. - static const size_t kMaxCachedFrames = 256 * 1024; - - // SbAudioSink callbacks - static void UpdateSourceStatusFunc(int* frames_in_buffer, - int* offset_in_frames, - bool* is_playing, - bool* is_eos_reached, - void* context); - static void ConsumeFramesFunc(int frames_consumed, void* context); - void UpdateSourceStatus(int* frames_in_buffer, - int* offset_in_frames, - bool* is_playing, - bool* is_eos_reached); - void ConsumeFrames(int frames_consumed); - - void AppendFrames(const float* source_buffer, int frames_to_append); - - const int channels_; - - Mutex mutex_; - bool paused_; - bool seeking_; - SbMediaTime seeking_to_pts_; - - std::vector<float> frame_buffer_; - float* frame_buffers_[1]; - int frames_in_buffer_; - int offset_in_frames_; - - int frames_consumed_; - bool end_of_stream_reached_; - - AudioDecoder* decoder_; - SbAudioSink audio_sink_; -}; - -} // namespace player -} // namespace starboard -} // namespace shared -} // namespace starboard - -#endif // STARBOARD_SHARED_STARBOARD_PLAYER_AUDIO_RENDERER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/audio_decoder_internal.h b/src/starboard/shared/starboard/player/filter/audio_decoder_internal.h new file mode 100644 index 0000000..cb1850c --- /dev/null +++ b/src/starboard/shared/starboard/player/filter/audio_decoder_internal.h
@@ -0,0 +1,67 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_DECODER_INTERNAL_H_ +#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_DECODER_INTERNAL_H_ + +#include <vector> + +#include "starboard/media.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/player/input_buffer_internal.h" +#include "starboard/types.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +// This class decodes encoded audio stream into playable audio data. +class AudioDecoder { + public: + virtual ~AudioDecoder() {} + + // Decode the encoded audio data stored in |input_buffer| and store the + // result in |output|. + virtual void Decode(const InputBuffer& input_buffer, + std::vector<uint8_t>* output) = 0; + // Note that there won't be more input data unless Reset() is called. + virtual void WriteEndOfStream() = 0; + // Clear any cached buffer of the codec and reset the state of the codec. + // This function will be called during seek to ensure that the left over + // data from previous buffers are cleared. + virtual void Reset() = 0; + + // Return the sample type of the decoded pcm data. + virtual SbMediaAudioSampleType GetSampleType() const = 0; + + // Return the sample rate of the incoming audio. This should be used by the + // audio renderer as the sample rate of the underlying audio stream can be + // different than the sample rate stored in the meta data. + virtual int GetSamplesPerSecond() const = 0; + + // Individual implementation has to implement this function to create an + // audio decoder. + static AudioDecoder* Create(SbMediaAudioCodec audio_codec, + const SbMediaAudioHeader& audio_header); +}; + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_DECODER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.cc b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.cc new file mode 100644 index 0000000..105e23f --- /dev/null +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.cc
@@ -0,0 +1,250 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/player/filter/audio_renderer_internal.h" + +#include <algorithm> + +#include "starboard/memory.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +namespace { +// TODO: This should be retrieved from the decoder. +// TODO: Make it not dependent on the frame size of AAC and HE-AAC. +const int kMaxFramesPerAccessUnit = 1024 * 2; +} // namespace + +AudioRenderer::AudioRenderer(scoped_ptr<AudioDecoder> decoder, + const SbMediaAudioHeader& audio_header) + : channels_(audio_header.number_of_channels), + bytes_per_frame_( + (decoder->GetSampleType() == kSbMediaAudioSampleTypeInt16 ? 2 : 4) * + channels_), + paused_(true), + seeking_(false), + seeking_to_pts_(0), + frame_buffer_(kMaxCachedFrames * bytes_per_frame_), + frames_in_buffer_(0), + offset_in_frames_(0), + frames_consumed_(0), + end_of_stream_reached_(false), + decoder_(decoder.Pass()), + audio_sink_(kSbAudioSinkInvalid) { + SB_DCHECK(decoder_ != NULL); + frame_buffers_[0] = &frame_buffer_[0]; +} + +AudioRenderer::~AudioRenderer() { + if (audio_sink_ != kSbAudioSinkInvalid) { + SbAudioSinkDestroy(audio_sink_); + } +} + +void AudioRenderer::WriteSample(const InputBuffer& input_buffer) { + if (end_of_stream_reached_) { + SB_LOG(ERROR) << "Appending audio sample at " << input_buffer.pts() + << " after EOS reached."; + return; + } + + SbMediaTime input_pts = input_buffer.pts(); + std::vector<uint8_t> decoded_audio; + decoder_->Decode(input_buffer, &decoded_audio); + if (decoded_audio.empty()) { + SB_DLOG(ERROR) << "decoded_audio contains no frames."; + return; + } + + { + ScopedLock lock(mutex_); + if (seeking_) { + if (input_pts < seeking_to_pts_) { + return; + } + } + + AppendFrames(&decoded_audio[0], decoded_audio.size() / bytes_per_frame_); + + if (seeking_ && frame_buffer_.size() > kPrerollFrames * bytes_per_frame_) { + seeking_ = false; + } + } + + // Create the audio sink if it is the first incoming AU after seeking. + if (audio_sink_ == kSbAudioSinkInvalid) { + int sample_rate = decoder_->GetSamplesPerSecond(); + // TODO: Implement resampler. + SB_DCHECK(sample_rate == + SbAudioSinkGetNearestSupportedSampleFrequency(sample_rate)); + // TODO: Handle sink creation failure. + audio_sink_ = SbAudioSinkCreate( + channels_, sample_rate, decoder_->GetSampleType(), + kSbMediaAudioFrameStorageTypeInterleaved, + reinterpret_cast<SbAudioSinkFrameBuffers>(frame_buffers_), + kMaxCachedFrames, &AudioRenderer::UpdateSourceStatusFunc, + &AudioRenderer::ConsumeFramesFunc, this); + } +} + +void AudioRenderer::WriteEndOfStream() { + SB_LOG_IF(WARNING, end_of_stream_reached_) + << "Try to write EOS after EOS is reached"; + if (end_of_stream_reached_) { + return; + } + end_of_stream_reached_ = true; + decoder_->WriteEndOfStream(); + + ScopedLock lock(mutex_); + // If we are seeking, we consider the seek is finished if end of stream is + // reached as there won't be any audio data in future. + if (seeking_) { + seeking_ = false; + } +} + +void AudioRenderer::Play() { + ScopedLock lock(mutex_); + paused_ = false; +} + +void AudioRenderer::Pause() { + ScopedLock lock(mutex_); + paused_ = true; +} + +void AudioRenderer::Seek(SbMediaTime seek_to_pts) { + SB_DCHECK(seek_to_pts >= 0); + + SbAudioSinkDestroy(audio_sink_); + audio_sink_ = kSbAudioSinkInvalid; + + seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0); + seeking_ = true; + frames_in_buffer_ = 0; + offset_in_frames_ = 0; + frames_consumed_ = 0; + end_of_stream_reached_ = false; + + decoder_->Reset(); + return; +} + +bool AudioRenderer::IsEndOfStreamPlayed() const { + return end_of_stream_reached_ && frames_in_buffer_ == 0; +} + +bool AudioRenderer::CanAcceptMoreData() const { + ScopedLock lock(mutex_); + return frames_in_buffer_ <= kMaxCachedFrames - kMaxFramesPerAccessUnit && + !end_of_stream_reached_; +} + +bool AudioRenderer::IsSeekingInProgress() const { + return seeking_; +} + +SbMediaTime AudioRenderer::GetCurrentTime() { + if (seeking_) { + return seeking_to_pts_; + } + return seeking_to_pts_ + + frames_consumed_ * kSbMediaTimeSecond / + decoder_->GetSamplesPerSecond(); +} + +// static +void AudioRenderer::UpdateSourceStatusFunc(int* frames_in_buffer, + int* offset_in_frames, + bool* is_playing, + bool* is_eos_reached, + void* context) { + AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context); + SB_DCHECK(audio_renderer); + SB_DCHECK(frames_in_buffer); + SB_DCHECK(offset_in_frames); + SB_DCHECK(is_playing); + SB_DCHECK(is_eos_reached); + + audio_renderer->UpdateSourceStatus(frames_in_buffer, offset_in_frames, + is_playing, is_eos_reached); +} + +// static +void AudioRenderer::ConsumeFramesFunc(int frames_consumed, void* context) { + AudioRenderer* audio_renderer = reinterpret_cast<AudioRenderer*>(context); + SB_DCHECK(audio_renderer); + + audio_renderer->ConsumeFrames(frames_consumed); +} + +void AudioRenderer::UpdateSourceStatus(int* frames_in_buffer, + int* offset_in_frames, + bool* is_playing, + bool* is_eos_reached) { + ScopedLock lock(mutex_); + + *is_eos_reached = end_of_stream_reached_; + + if (paused_ || seeking_) { + *is_playing = false; + *frames_in_buffer = *offset_in_frames = 0; + return; + } + + *is_playing = true; + *frames_in_buffer = frames_in_buffer_; + *offset_in_frames = offset_in_frames_; +} + +void AudioRenderer::ConsumeFrames(int frames_consumed) { + ScopedLock lock(mutex_); + + SB_DCHECK(frames_consumed <= frames_in_buffer_); + offset_in_frames_ += frames_consumed; + offset_in_frames_ %= kMaxCachedFrames; + frames_in_buffer_ -= frames_consumed; + frames_consumed_ += frames_consumed; +} + +void AudioRenderer::AppendFrames(const uint8_t* source_buffer, + int frames_to_append) { + SB_DCHECK(frames_in_buffer_ + frames_to_append <= kMaxCachedFrames); + + int offset_to_append = + (offset_in_frames_ + frames_in_buffer_) % kMaxCachedFrames; + if (frames_to_append > kMaxCachedFrames - offset_to_append) { + SbMemoryCopy(&frame_buffer_[offset_to_append * bytes_per_frame_], + source_buffer, + (kMaxCachedFrames - offset_to_append) * bytes_per_frame_); + source_buffer += (kMaxCachedFrames - offset_to_append) * bytes_per_frame_; + frames_to_append -= kMaxCachedFrames - offset_to_append; + frames_in_buffer_ += kMaxCachedFrames - offset_to_append; + offset_to_append = 0; + } + SbMemoryCopy(&frame_buffer_[offset_to_append * bytes_per_frame_], + source_buffer, frames_to_append * bytes_per_frame_); + frames_in_buffer_ += frames_to_append; +} + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h new file mode 100644 index 0000000..e14457c --- /dev/null +++ b/src/starboard/shared/starboard/player/filter/audio_renderer_internal.h
@@ -0,0 +1,108 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_INTERNAL_H_ +#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_INTERNAL_H_ + +#include <vector> + +#include "starboard/audio_sink.h" +#include "starboard/common/scoped_ptr.h" +#include "starboard/log.h" +#include "starboard/media.h" +#include "starboard/mutex.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h" +#include "starboard/shared/starboard/player/input_buffer_internal.h" +#include "starboard/types.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +class AudioRenderer { + public: + AudioRenderer(scoped_ptr<AudioDecoder> decoder, + const SbMediaAudioHeader& audio_header); + ~AudioRenderer(); + + bool is_valid() const { return true; } + + void WriteSample(const InputBuffer& input_buffer); + void WriteEndOfStream(); + + void Play(); + void Pause(); + void Seek(SbMediaTime seek_to_pts); + + bool IsEndOfStreamWritten() const { return end_of_stream_reached_; } + bool IsEndOfStreamPlayed() const; + bool CanAcceptMoreData() const; + bool IsSeekingInProgress() const; + SbMediaTime GetCurrentTime(); + + private: + // Preroll considered finished after either kPrerollFrames is cached or EOS + // is reached. + static const size_t kPrerollFrames = 64 * 1024; + // Set a soft limit for the max audio frames we can cache so we can: + // 1. Avoid using too much memory. + // 2. Have the audio cache full to simulate the state that the renderer can + // no longer accept more data. + static const size_t kMaxCachedFrames = 256 * 1024; + + // SbAudioSink callbacks + static void UpdateSourceStatusFunc(int* frames_in_buffer, + int* offset_in_frames, + bool* is_playing, + bool* is_eos_reached, + void* context); + static void ConsumeFramesFunc(int frames_consumed, void* context); + void UpdateSourceStatus(int* frames_in_buffer, + int* offset_in_frames, + bool* is_playing, + bool* is_eos_reached); + void ConsumeFrames(int frames_consumed); + + void AppendFrames(const uint8_t* source_buffer, int frames_to_append); + + const int channels_; + const int bytes_per_frame_; + + Mutex mutex_; + bool paused_; + bool seeking_; + SbMediaTime seeking_to_pts_; + + std::vector<uint8_t> frame_buffer_; + uint8_t* frame_buffers_[1]; + int frames_in_buffer_; + int offset_in_frames_; + + int frames_consumed_; + bool end_of_stream_reached_; + + scoped_ptr<AudioDecoder> decoder_; + SbAudioSink audio_sink_; +}; + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_AUDIO_RENDERER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc new file mode 100644 index 0000000..b73cbb2 --- /dev/null +++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.cc
@@ -0,0 +1,242 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h" + +#include "starboard/log.h" +#include "starboard/shared/starboard/application.h" +#include "starboard/shared/starboard/drm/drm_system_internal.h" +#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h" +#include "starboard/shared/starboard/player/filter/video_decoder_internal.h" +#include "starboard/shared/starboard/player/input_buffer_internal.h" +#include "starboard/shared/starboard/player/video_frame_internal.h" +#include "starboard/time.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +FilterBasedPlayerWorkerHandler::FilterBasedPlayerWorkerHandler( + SbMediaVideoCodec video_codec, + SbMediaAudioCodec audio_codec, + SbDrmSystem drm_system, + const SbMediaAudioHeader& audio_header) + : player_worker_(NULL), + player_(kSbPlayerInvalid), + update_media_time_cb_(NULL), + get_player_state_cb_(NULL), + update_player_state_cb_(NULL), + video_codec_(video_codec), + audio_codec_(audio_codec), + drm_system_(drm_system), + audio_header_(audio_header), + paused_(false) {} + +void FilterBasedPlayerWorkerHandler::Setup( + PlayerWorker* player_worker, + SbPlayer player, + UpdateMediaTimeCB update_media_time_cb, + GetPlayerStateCB get_player_state_cb, + UpdatePlayerStateCB update_player_state_cb) { + // This function should only be called once. + SB_DCHECK(player_worker_ == NULL); + + // All parameters has to be valid. + SB_DCHECK(player_worker); + SB_DCHECK(SbPlayerIsValid(player)); + SB_DCHECK(update_media_time_cb); + SB_DCHECK(get_player_state_cb); + SB_DCHECK(update_player_state_cb); + + player_worker_ = player_worker; + player_ = player; + update_media_time_cb_ = update_media_time_cb; + get_player_state_cb_ = get_player_state_cb; + update_player_state_cb_ = update_player_state_cb; +} + +bool FilterBasedPlayerWorkerHandler::ProcessInitEvent() { + scoped_ptr<AudioDecoder> audio_decoder( + AudioDecoder::Create(audio_codec_, audio_header_)); + scoped_ptr<VideoDecoder> video_decoder(VideoDecoder::Create(video_codec_)); + + if (!audio_decoder || !video_decoder) { + return false; + } + + audio_renderer_.reset(new AudioRenderer(audio_decoder.Pass(), audio_header_)); + video_renderer_.reset(new VideoRenderer(video_decoder.Pass())); + if (audio_renderer_->is_valid() && video_renderer_->is_valid()) { + return true; + } + + audio_renderer_.reset(NULL); + video_renderer_.reset(NULL); + return false; +} + +bool FilterBasedPlayerWorkerHandler::ProcessSeekEvent( + const SeekEventData& data) { + SbMediaTime seek_to_pts = data.seek_to_pts; + if (seek_to_pts < 0) { + SB_DLOG(ERROR) << "Try to seek to negative timestamp " << seek_to_pts; + seek_to_pts = 0; + } + + audio_renderer_->Pause(); + audio_renderer_->Seek(seek_to_pts); + video_renderer_->Seek(seek_to_pts); + return true; +} + +bool FilterBasedPlayerWorkerHandler::ProcessWriteSampleEvent( + const WriteSampleEventData& data, + bool* written) { + SB_DCHECK(written != NULL); + + *written = true; + + if (data.sample_type == kSbMediaTypeAudio) { + if (audio_renderer_->IsEndOfStreamWritten()) { + SB_LOG(WARNING) << "Try to write audio sample after EOS is reached"; + } else { + if (!audio_renderer_->CanAcceptMoreData()) { + *written = false; + return true; + } + + if (data.input_buffer->drm_info()) { + if (!SbDrmSystemIsValid(drm_system_)) { + return false; + } + if (drm_system_->Decrypt(data.input_buffer) == + SbDrmSystemPrivate::kRetry) { + *written = false; + return true; + } + } + audio_renderer_->WriteSample(*data.input_buffer); + } + } else { + SB_DCHECK(data.sample_type == kSbMediaTypeVideo); + if (video_renderer_->IsEndOfStreamWritten()) { + SB_LOG(WARNING) << "Try to write video sample after EOS is reached"; + } else { + if (!video_renderer_->CanAcceptMoreData()) { + *written = false; + return true; + } + if (data.input_buffer->drm_info()) { + if (!SbDrmSystemIsValid(drm_system_)) { + return false; + } + if (drm_system_->Decrypt(data.input_buffer) == + SbDrmSystemPrivate::kRetry) { + *written = false; + return true; + } + } + video_renderer_->WriteSample(*data.input_buffer); + } + } + + return true; +} + +bool FilterBasedPlayerWorkerHandler::ProcessWriteEndOfStreamEvent( + const WriteEndOfStreamEventData& data) { + if (data.stream_type == kSbMediaTypeAudio) { + if (audio_renderer_->IsEndOfStreamWritten()) { + SB_LOG(WARNING) << "Try to write audio EOS after EOS is enqueued"; + } else { + SB_LOG(INFO) << "Audio EOS enqueued"; + audio_renderer_->WriteEndOfStream(); + } + } else { + if (video_renderer_->IsEndOfStreamWritten()) { + SB_LOG(WARNING) << "Try to write video EOS after EOS is enqueued"; + } else { + SB_LOG(INFO) << "Video EOS enqueued"; + video_renderer_->WriteEndOfStream(); + } + } + + return true; +} + +bool FilterBasedPlayerWorkerHandler::ProcessSetPauseEvent( + const SetPauseEventData& data) { + paused_ = data.pause; + + if (data.pause) { + audio_renderer_->Pause(); + SB_DLOG(INFO) << "Playback paused."; + } else { + audio_renderer_->Play(); + SB_DLOG(INFO) << "Playback started."; + } + + return true; +} + +bool FilterBasedPlayerWorkerHandler::ProcessUpdateEvent( + const SetBoundsEventData& data) { + if ((*player_worker_.*get_player_state_cb_)() == kSbPlayerStatePrerolling) { + if (!audio_renderer_->IsSeekingInProgress() && + !video_renderer_->IsSeekingInProgress()) { + (*player_worker_.*update_player_state_cb_)(kSbPlayerStatePresenting); + if (!paused_) { + audio_renderer_->Play(); + } + } + } + + if ((*player_worker_.*get_player_state_cb_)() == kSbPlayerStatePresenting) { + if (audio_renderer_->IsEndOfStreamPlayed() && + video_renderer_->IsEndOfStreamPlayed()) { + (*player_worker_.*update_player_state_cb_)(kSbPlayerStateEndOfStream); + } + + scoped_refptr<VideoFrame> frame = + video_renderer_->GetCurrentFrame(audio_renderer_->GetCurrentTime()); + +#if SB_IS(PLAYER_PUNCHED_OUT) + shared::starboard::Application::Get()->HandleFrame( + player_, frame, data.x, data.y, data.width, data.height); +#endif // SB_IS(PLAYER_PUNCHED_OUT) + + (*player_worker_.*update_media_time_cb_)(audio_renderer_->GetCurrentTime()); + } + + return true; +} + +void FilterBasedPlayerWorkerHandler::ProcessStopEvent() { + audio_renderer_.reset(); + video_renderer_.reset(); + +#if SB_IS(PLAYER_PUNCHED_OUT) + // Clear the video frame as we terminate. + shared::starboard::Application::Get()->HandleFrame( + player_, VideoFrame::CreateEOSFrame(), 0, 0, 0, 0); +#endif // SB_IS(PLAYER_PUNCHED_OUT) +} + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h new file mode 100644 index 0000000..cde0391 --- /dev/null +++ b/src/starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h
@@ -0,0 +1,80 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_FILTER_BASED_PLAYER_WORKER_HANDLER_H_ +#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_FILTER_BASED_PLAYER_WORKER_HANDLER_H_ + +#include "starboard/common/scoped_ptr.h" +#include "starboard/configuration.h" +#include "starboard/media.h" +#include "starboard/player.h" +#include "starboard/shared/starboard/player/filter/audio_renderer_internal.h" +#include "starboard/shared/starboard/player/filter/video_renderer_internal.h" +#include "starboard/shared/starboard/player/input_buffer_internal.h" +#include "starboard/shared/starboard/player/player_worker.h" +#include "starboard/time.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +class FilterBasedPlayerWorkerHandler : public PlayerWorker::Handler { + public: + FilterBasedPlayerWorkerHandler(SbMediaVideoCodec video_codec, + SbMediaAudioCodec audio_codec, + SbDrmSystem drm_system, + const SbMediaAudioHeader& audio_header); + + private: + void Setup(PlayerWorker* player_worker, + SbPlayer player, + UpdateMediaTimeCB update_media_time_cb, + GetPlayerStateCB get_player_state_cb, + UpdatePlayerStateCB update_player_state_cb) SB_OVERRIDE; + bool ProcessInitEvent() SB_OVERRIDE; + bool ProcessSeekEvent(const SeekEventData& data) SB_OVERRIDE; + bool ProcessWriteSampleEvent(const WriteSampleEventData& data, + bool* written) SB_OVERRIDE; + bool ProcessWriteEndOfStreamEvent(const WriteEndOfStreamEventData& data) + SB_OVERRIDE; + bool ProcessSetPauseEvent(const SetPauseEventData& data) SB_OVERRIDE; + bool ProcessUpdateEvent(const SetBoundsEventData& data) SB_OVERRIDE; + void ProcessStopEvent() SB_OVERRIDE; + + PlayerWorker* player_worker_; + SbPlayer player_; + UpdateMediaTimeCB update_media_time_cb_; + GetPlayerStateCB get_player_state_cb_; + UpdatePlayerStateCB update_player_state_cb_; + + SbMediaVideoCodec video_codec_; + SbMediaAudioCodec audio_codec_; + SbDrmSystem drm_system_; + SbMediaAudioHeader audio_header_; + + scoped_ptr<AudioRenderer> audio_renderer_; + scoped_ptr<VideoRenderer> video_renderer_; + + bool paused_; +}; + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_FILTER_BASED_PLAYER_WORKER_HANDLER_H_
diff --git a/src/starboard/shared/starboard/player/filter/video_decoder_internal.h b/src/starboard/shared/starboard/player/filter/video_decoder_internal.h new file mode 100644 index 0000000..e55c685 --- /dev/null +++ b/src/starboard/shared/starboard/player/filter/video_decoder_internal.h
@@ -0,0 +1,76 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_DECODER_INTERNAL_H_ +#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_DECODER_INTERNAL_H_ + +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/player/input_buffer_internal.h" +#include "starboard/shared/starboard/player/video_frame_internal.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +// This class decodes encoded video stream into video frames. +class VideoDecoder { + public: + enum Status { kNeedMoreInput, kBufferFull, kFatalError }; + + class Host { + public: + // |frame| can contain a decoded frame or be NULL when |status| is not + // kFatalError. When status is kFatalError, |frame| will be NULL. Its + // user should only call WriteInputFrame() when |status| is kNeedMoreInput + // or when the instance is just created. Also note that calling Reset() or + // dtor from this callback will result in deadlock. + virtual void OnDecoderStatusUpdate( + Status status, + const scoped_refptr<VideoFrame>& frame) = 0; + + protected: + ~Host() {} + }; + + virtual ~VideoDecoder() {} + + virtual void SetHost(Host* host) = 0; + + // Send encoded video frame stored in |input_buffer| to decode. + virtual void WriteInputBuffer(const InputBuffer& input_buffer) = 0; + // Note that there won't be more input data unless Reset() is called. + // OnDecoderStatusUpdate will still be called on Host during flushing until + // the |frame| is an EOS frame. + virtual void WriteEndOfStream() = 0; + // Clear any cached buffer of the codec and reset the state of the codec. + // This function will be called during seek to ensure that there is no left + // over data from previous buffers. No DecoderStatusFunc call will be made + // after this function returns unless WriteInputFrame() or WriteEndOfStream() + // is called again. + virtual void Reset() = 0; + + // Individual implementation has to implement this function to create a video + // decoder. + static VideoDecoder* Create(SbMediaVideoCodec video_codec); +}; + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_DECODER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc b/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc new file mode 100644 index 0000000..35b6bf0 --- /dev/null +++ b/src/starboard/shared/starboard/player/filter/video_renderer_internal.cc
@@ -0,0 +1,145 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/shared/starboard/player/filter/video_renderer_internal.h" + +#include <algorithm> + +namespace starboard { +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +VideoRenderer::VideoRenderer(scoped_ptr<VideoDecoder> decoder) + : seeking_(false), + seeking_to_pts_(0), + end_of_stream_written_(false), + need_more_input_(true), + decoder_(decoder.Pass()) { + SB_DCHECK(decoder_ != NULL); + decoder_->SetHost(this); +} + +void VideoRenderer::WriteSample(const InputBuffer& input_buffer) { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + if (end_of_stream_written_) { + SB_LOG(ERROR) << "Appending video sample at " << input_buffer.pts() + << " after EOS reached."; + return; + } + + { + ScopedLock lock(mutex_); + need_more_input_ = false; + } + + decoder_->WriteInputBuffer(input_buffer); +} + +void VideoRenderer::WriteEndOfStream() { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + SB_LOG_IF(WARNING, end_of_stream_written_) + << "Try to write EOS after EOS is reached"; + if (end_of_stream_written_) { + return; + } + end_of_stream_written_ = true; + decoder_->WriteEndOfStream(); +} + +void VideoRenderer::Seek(SbMediaTime seek_to_pts) { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + SB_DCHECK(seek_to_pts >= 0); + + decoder_->Reset(); + + ScopedLock lock(mutex_); + + seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0); + seeking_ = true; + end_of_stream_written_ = false; + + if (!frames_.empty()) { + seeking_frame_ = frames_.front(); + } + frames_.clear(); +} + +scoped_refptr<VideoFrame> VideoRenderer::GetCurrentFrame( + SbMediaTime media_time) { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + + if (frames_.empty()) { + return seeking_frame_; + } + // Remove any frames with timestamps earlier than |media_time|, but always + // keep at least one of the frames. + while (frames_.size() > 1 && frames_.front()->pts() < media_time) { + frames_.pop_front(); + } + + return frames_.front(); +} + +bool VideoRenderer::IsEndOfStreamPlayed() const { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + return end_of_stream_written_ && frames_.size() <= 1; +} + +bool VideoRenderer::CanAcceptMoreData() const { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + ScopedLock lock(mutex_); + return frames_.size() < kMaxCachedFrames && !end_of_stream_written_ && + need_more_input_; +} + +bool VideoRenderer::IsSeekingInProgress() const { + SB_DCHECK(thread_checker_.CalledOnValidThread()); + return seeking_; +} + +void VideoRenderer::OnDecoderStatusUpdate( + VideoDecoder::Status status, + const scoped_refptr<VideoFrame>& frame) { + ScopedLock lock(mutex_); + + if (frame) { + bool frame_too_early = false; + if (seeking_) { + if (frame->IsEndOfStream()) { + seeking_ = false; + } else if (frame->pts() < seeking_to_pts_) { + frame_too_early = true; + } + } + if (!frame_too_early) { + frames_.push_back(frame); + } + + if (seeking_ && frames_.size() >= kPrerollFrames) { + seeking_ = false; + } + } + + need_more_input_ = (status == VideoDecoder::kNeedMoreInput); +} + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared +} // namespace starboard
diff --git a/src/starboard/shared/starboard/player/filter/video_renderer_internal.h b/src/starboard/shared/starboard/player/filter/video_renderer_internal.h new file mode 100644 index 0000000..6cc4cad --- /dev/null +++ b/src/starboard/shared/starboard/player/filter/video_renderer_internal.h
@@ -0,0 +1,96 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_RENDERER_INTERNAL_H_ +#define STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_RENDERER_INTERNAL_H_ + +#include <list> + +#include "starboard/common/scoped_ptr.h" +#include "starboard/log.h" +#include "starboard/media.h" +#include "starboard/mutex.h" +#include "starboard/shared/internal_only.h" +#include "starboard/shared/starboard/player/filter/video_decoder_internal.h" +#include "starboard/shared/starboard/player/input_buffer_internal.h" +#include "starboard/shared/starboard/player/video_frame_internal.h" +#include "starboard/shared/starboard/thread_checker.h" + +namespace starboard { +namespace shared { +namespace starboard { +namespace player { +namespace filter { + +class VideoRenderer : private VideoDecoder::Host { + public: + explicit VideoRenderer(scoped_ptr<VideoDecoder> decoder); + + bool is_valid() const { return true; } + + void WriteSample(const InputBuffer& input_buffer); + void WriteEndOfStream(); + + void Seek(SbMediaTime seek_to_pts); + + scoped_refptr<VideoFrame> GetCurrentFrame(SbMediaTime media_time); + + bool IsEndOfStreamWritten() const { return end_of_stream_written_; } + bool IsEndOfStreamPlayed() const; + bool CanAcceptMoreData() const; + bool IsSeekingInProgress() const; + + private: + typedef std::list<scoped_refptr<VideoFrame> > Frames; + + // Preroll considered finished after either kPrerollFrames is cached or EOS + // is reached. + static const size_t kPrerollFrames = 1; + // Set a soft limit for the max video frames we can cache so we can: + // 1. Avoid using too much memory. + // 2. Have the frame cache full to simulate the state that the renderer can + // no longer accept more data. + static const size_t kMaxCachedFrames = 8; + + // VideoDecoder::Host method. + void OnDecoderStatusUpdate(VideoDecoder::Status status, + const scoped_refptr<VideoFrame>& frame) + SB_OVERRIDE; + + ThreadChecker thread_checker_; + ::starboard::Mutex mutex_; + + bool seeking_; + Frames frames_; + + // During seeking, all frames inside |frames_| will be cleared but the app + // should still display the last frame it is rendering. This frame will be + // kept inside |seeking_frame_|. It is an empty/black frame before the video + // is started. + scoped_refptr<VideoFrame> seeking_frame_; + + SbMediaTime seeking_to_pts_; + bool end_of_stream_written_; + bool need_more_input_; + + scoped_ptr<VideoDecoder> decoder_; +}; + +} // namespace filter +} // namespace player +} // namespace starboard +} // namespace shared +} // namespace starboard + +#endif // STARBOARD_SHARED_STARBOARD_PLAYER_FILTER_VIDEO_RENDERER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/input_buffer_internal.cc b/src/starboard/shared/starboard/player/input_buffer_internal.cc index 45a65c9..8431fc2 100644 --- a/src/starboard/shared/starboard/player/input_buffer_internal.cc +++ b/src/starboard/shared/starboard/player/input_buffer_internal.cc
@@ -49,7 +49,14 @@ video_sample_info_ = *video_sample_info; } if (has_drm_info_) { + SB_DCHECK(sample_drm_info->subsample_count > 0); + + subsamples_.assign(sample_drm_info->subsample_mapping, + sample_drm_info->subsample_mapping + + sample_drm_info->subsample_count); drm_info_ = *sample_drm_info; + drm_info_.subsample_mapping = + subsamples_.empty() ? NULL : &subsamples_[0]; } } @@ -107,6 +114,7 @@ bool has_drm_info_; SbDrmSampleInfo drm_info_; std::vector<uint8_t> decrypted_data_; + std::vector<SbDrmSubSampleMapping> subsamples_; SB_DISALLOW_COPY_AND_ASSIGN(ReferenceCountedBuffer); };
diff --git a/src/starboard/shared/starboard/player/input_buffer_internal.h b/src/starboard/shared/starboard/player/input_buffer_internal.h index 273f873..5cc7af6 100644 --- a/src/starboard/shared/starboard/player/input_buffer_internal.h +++ b/src/starboard/shared/starboard/player/input_buffer_internal.h
@@ -15,6 +15,7 @@ #ifndef STARBOARD_SHARED_STARBOARD_PLAYER_INPUT_BUFFER_INTERNAL_H_ #define STARBOARD_SHARED_STARBOARD_PLAYER_INPUT_BUFFER_INTERNAL_H_ +#include "starboard/drm.h" #include "starboard/media.h" #include "starboard/player.h" #include "starboard/shared/internal_only.h"
diff --git a/src/starboard/shared/starboard/player/player_create.cc b/src/starboard/shared/starboard/player/player_create.cc index 8489cce..c09f526 100644 --- a/src/starboard/shared/starboard/player/player_create.cc +++ b/src/starboard/shared/starboard/player/player_create.cc
@@ -14,8 +14,16 @@ #include "starboard/player.h" +#include "starboard/configuration.h" +#include "starboard/decode_target.h" #include "starboard/log.h" +#include "starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h" #include "starboard/shared/starboard/player/player_internal.h" +#include "starboard/shared/starboard/player/player_worker.h" + +using starboard::shared::starboard::player::filter:: + FilterBasedPlayerWorkerHandler; +using starboard::shared::starboard::player::PlayerWorker; SbPlayer SbPlayerCreate(SbWindow window, SbMediaVideoCodec video_codec, @@ -26,7 +34,17 @@ SbPlayerDeallocateSampleFunc sample_deallocate_func, SbPlayerDecoderStatusFunc decoder_status_func, SbPlayerStatusFunc player_status_func, - void* context) { + void* context +#if SB_VERSION(3) + , + SbDecodeTargetProvider* provider +#endif + ) { + SB_UNREFERENCED_PARAMETER(window); +#if SB_VERSION(3) + SB_UNREFERENCED_PARAMETER(provider); +#endif + if (audio_codec != kSbMediaAudioCodecAac) { SB_LOG(ERROR) << "Unsupported audio codec " << audio_codec; return kSbPlayerInvalid; @@ -42,7 +60,10 @@ return kSbPlayerInvalid; } - return new SbPlayerPrivate(window, video_codec, audio_codec, duration_pts, - drm_system, audio_header, sample_deallocate_func, - decoder_status_func, player_status_func, context); + starboard::scoped_ptr<PlayerWorker::Handler> handler( + new FilterBasedPlayerWorkerHandler(video_codec, audio_codec, drm_system, + *audio_header)); + return new SbPlayerPrivate(duration_pts, sample_deallocate_func, + decoder_status_func, player_status_func, context, + handler.Pass()); }
diff --git a/src/starboard/shared/starboard/player/player_internal.cc b/src/starboard/shared/starboard/player/player_internal.cc index 2c7c9f6..bc9d74d 100644 --- a/src/starboard/shared/starboard/player/player_internal.cc +++ b/src/starboard/shared/starboard/player/player_internal.cc
@@ -17,28 +17,26 @@ #include "starboard/log.h" using starboard::shared::starboard::player::InputBuffer; -using starboard::shared::starboard::player::PlayerWorker; namespace { +const SbTime kUpdateInterval = 5 * kSbTimeMillisecond; + SbMediaTime GetMediaTime(SbMediaTime media_pts, SbTimeMonotonic media_pts_update_time) { SbTimeMonotonic elapsed = SbTimeGetMonotonicNow() - media_pts_update_time; return media_pts + elapsed * kSbMediaTimeSecond / kSbTimeSecond; } -} + +} // namespace SbPlayerPrivate::SbPlayerPrivate( - SbWindow window, - SbMediaVideoCodec video_codec, - SbMediaAudioCodec audio_codec, SbMediaTime duration_pts, - SbDrmSystem drm_system, - const SbMediaAudioHeader* audio_header, SbPlayerDeallocateSampleFunc sample_deallocate_func, SbPlayerDecoderStatusFunc decoder_status_func, SbPlayerStatusFunc player_status_func, - void* context) + void* context, + starboard::scoped_ptr<PlayerWorker::Handler> player_worker_handler) : sample_deallocate_func_(sample_deallocate_func), context_(context), ticket_(SB_PLAYER_INITIAL_TICKET), @@ -50,16 +48,13 @@ is_paused_(true), volume_(1.0), total_video_frames_(0), - worker_(this, - window, - video_codec, - audio_codec, - drm_system, - *audio_header, - decoder_status_func, - player_status_func, - this, - context) {} + worker_(new PlayerWorker(this, + player_worker_handler.Pass(), + decoder_status_func, + player_status_func, + this, + kUpdateInterval, + context)) {} void SbPlayerPrivate::Seek(SbMediaTime seek_to_pts, int ticket) { { @@ -71,7 +66,7 @@ } PlayerWorker::SeekEventData data = {seek_to_pts, ticket}; - worker_.EnqueueEvent(data); + worker_->EnqueueEvent(data); } void SbPlayerPrivate::WriteSample( @@ -88,18 +83,18 @@ sample_deallocate_func_, this, context_, sample_buffer, sample_buffer_size, sample_pts, video_sample_info, sample_drm_info); PlayerWorker::WriteSampleEventData data = {sample_type, input_buffer}; - worker_.EnqueueEvent(data); + worker_->EnqueueEvent(data); } void SbPlayerPrivate::WriteEndOfStream(SbMediaType stream_type) { PlayerWorker::WriteEndOfStreamEventData data = {stream_type}; - worker_.EnqueueEvent(data); + worker_->EnqueueEvent(data); } #if SB_IS(PLAYER_PUNCHED_OUT) void SbPlayerPrivate::SetBounds(int x, int y, int width, int height) { PlayerWorker::SetBoundsEventData data = {x, y, width, height}; - worker_.EnqueueEvent(data); + worker_->EnqueueEvent(data); // TODO: Wait until a frame is rendered with the updated bounds. } #endif @@ -126,7 +121,7 @@ void SbPlayerPrivate::SetPause(bool pause) { PlayerWorker::SetPauseEventData data = {pause}; - worker_.EnqueueEvent(data); + worker_->EnqueueEvent(data); } void SbPlayerPrivate::SetVolume(double volume) {
diff --git a/src/starboard/shared/starboard/player/player_internal.h b/src/starboard/shared/starboard/player/player_internal.h index 7ecc299..4a8a917 100644 --- a/src/starboard/shared/starboard/player/player_internal.h +++ b/src/starboard/shared/starboard/player/player_internal.h
@@ -15,6 +15,7 @@ #ifndef STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_INTERNAL_H_ #define STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_INTERNAL_H_ +#include "starboard/common/scoped_ptr.h" #include "starboard/media.h" #include "starboard/player.h" #include "starboard/shared/internal_only.h" @@ -22,20 +23,18 @@ #include "starboard/time.h" #include "starboard/window.h" -// TODO: Implement DRM support struct SbPlayerPrivate : starboard::shared::starboard::player::PlayerWorker::Host { public: - SbPlayerPrivate(SbWindow window, - SbMediaVideoCodec video_codec, - SbMediaAudioCodec audio_codec, - SbMediaTime duration_pts, - SbDrmSystem drm_system, - const SbMediaAudioHeader* audio_header, - SbPlayerDeallocateSampleFunc sample_deallocate_func, - SbPlayerDecoderStatusFunc decoder_status_func, - SbPlayerStatusFunc player_status_func, - void* context); + typedef starboard::shared::starboard::player::PlayerWorker PlayerWorker; + + SbPlayerPrivate( + SbMediaTime duration_pts, + SbPlayerDeallocateSampleFunc sample_deallocate_func, + SbPlayerDecoderStatusFunc decoder_status_func, + SbPlayerStatusFunc player_status_func, + void* context, + starboard::scoped_ptr<PlayerWorker::Handler> player_worker_handler); void Seek(SbMediaTime seek_to_pts, int ticket); void WriteSample(SbMediaType sample_type, @@ -71,7 +70,7 @@ double volume_; int total_video_frames_; - starboard::shared::starboard::player::PlayerWorker worker_; + starboard::scoped_ptr<PlayerWorker> worker_; }; #endif // STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/player_worker.cc b/src/starboard/shared/starboard/player/player_worker.cc index 5bab32b..51c869d 100644 --- a/src/starboard/shared/starboard/player/player_worker.cc +++ b/src/starboard/shared/starboard/player/player_worker.cc
@@ -14,14 +14,8 @@ #include "starboard/shared/starboard/player/player_worker.h" -#include <algorithm> - -#include "starboard/shared/starboard/application.h" -#include "starboard/shared/starboard/drm/drm_system_internal.h" -#include "starboard/shared/starboard/player/audio_decoder_internal.h" -#include "starboard/shared/starboard/player/input_buffer_internal.h" -#include "starboard/shared/starboard/player/video_decoder_internal.h" -#include "starboard/shared/starboard/player/video_frame_internal.h" +#include "starboard/common/reset_and_return.h" +#include "starboard/memory.h" namespace starboard { namespace shared { @@ -29,34 +23,34 @@ namespace player { PlayerWorker::PlayerWorker(Host* host, - SbWindow window, - SbMediaVideoCodec video_codec, - SbMediaAudioCodec audio_codec, - SbDrmSystem drm_system, - const SbMediaAudioHeader& audio_header, + scoped_ptr<Handler> handler, SbPlayerDecoderStatusFunc decoder_status_func, SbPlayerStatusFunc player_status_func, SbPlayer player, + SbTime update_interval, void* context) - : host_(host), - window_(window), - video_codec_(video_codec), - audio_codec_(audio_codec), - drm_system_(drm_system), - audio_header_(audio_header), + : thread_(kSbThreadInvalid), + host_(host), + handler_(handler.Pass()), decoder_status_func_(decoder_status_func), player_status_func_(player_status_func), player_(player), context_(context), - audio_renderer_(NULL), - video_renderer_(NULL), - audio_decoder_state_(kSbPlayerDecoderStateBufferFull), - video_decoder_state_(kSbPlayerDecoderStateBufferFull), ticket_(SB_PLAYER_INITIAL_TICKET), - paused_(true), - player_state_(kSbPlayerStateInitialized) { - SB_DCHECK(host != NULL); + player_state_(kSbPlayerStateInitialized), + update_interval_(update_interval) { + SB_DCHECK(host_ != NULL); + SB_DCHECK(handler_ != NULL); + SB_DCHECK(update_interval_ > 0); + handler_->Setup(this, player_, &PlayerWorker::UpdateMediaTime, + &PlayerWorker::player_state, + &PlayerWorker::UpdatePlayerState); + + pending_audio_sample_.input_buffer = NULL; + pending_video_sample_.input_buffer = NULL; + + SB_DCHECK(!SbThreadIsValid(thread_)); thread_ = SbThreadCreate(0, kSbThreadPriorityHigh, kSbThreadNoAffinity, true, "player_worker", &PlayerWorker::ThreadEntryPoint, this); @@ -68,9 +62,41 @@ PlayerWorker::~PlayerWorker() { queue_.Put(new Event(Event::kStop)); SbThreadJoin(thread_, NULL); + thread_ = kSbThreadInvalid; + // Now the whole pipeline has been torn down and no callback will be called. // The caller can ensure that upon the return of SbPlayerDestroy() all side // effects are gone. + + // There can be events inside the queue that is not processed. Clean them up. + while (Event* event = queue_.GetTimed(0)) { + if (event->type == Event::kWriteSample) { + delete event->data.write_sample.input_buffer; + } + delete event; + } + + if (pending_audio_sample_.input_buffer != NULL) { + delete pending_audio_sample_.input_buffer; + } + + if (pending_video_sample_.input_buffer != NULL) { + delete pending_video_sample_.input_buffer; + } +} + +void PlayerWorker::UpdateMediaTime(SbMediaTime time) { + host_->UpdateMediaTime(time, ticket_); +} + +void PlayerWorker::UpdatePlayerState(SbPlayerState player_state) { + player_state_ = player_state; + + if (!player_status_func_) { + return; + } + + player_status_func_(player_, context_, player_state_, ticket_); } // static @@ -85,38 +111,35 @@ SB_DCHECK(event != NULL); SB_DCHECK(event->type == Event::kInit); delete event; - bool running = ProcessInitEvent(); + bool running = DoInit(); SetBoundsEventData bounds = {0, 0, 0, 0}; while (running) { - Event* event = queue_.GetTimed(kUpdateInterval); + Event* event = queue_.GetTimed(update_interval_); if (event == NULL) { - running &= ProcessUpdateEvent(bounds); + running &= DoUpdate(bounds); continue; } SB_DCHECK(event->type != Event::kInit); if (event->type == Event::kSeek) { - running &= ProcessSeekEvent(event->data.seek); + running &= DoSeek(event->data.seek); } else if (event->type == Event::kWriteSample) { - bool retry; - running &= ProcessWriteSampleEvent(event->data.write_sample, &retry); - if (retry && running) { - EnqueueEvent(event->data.write_sample); - } else { - delete event->data.write_sample.input_buffer; - } + running &= DoWriteSample(event->data.write_sample); } else if (event->type == Event::kWriteEndOfStream) { - running &= ProcessWriteEndOfStreamEvent(event->data.write_end_of_stream); + running &= DoWriteEndOfStream(event->data.write_end_of_stream); } else if (event->type == Event::kSetPause) { - running &= ProcessSetPauseEvent(event->data.set_pause); + running &= handler_->ProcessSetPauseEvent(event->data.set_pause); } else if (event->type == Event::kSetBounds) { - bounds = event->data.set_bounds; - ProcessUpdateEvent(bounds); + if (SbMemoryCompare(&bounds, &event->data.set_bounds, sizeof(bounds)) != + 0) { + bounds = event->data.set_bounds; + running &= DoUpdate(bounds); + } } else if (event->type == Event::kStop) { - ProcessStopEvent(); + DoStop(); running = false; } else { SB_NOTREACHED() << "event type " << event->type; @@ -125,34 +148,17 @@ } } -bool PlayerWorker::ProcessInitEvent() { - AudioDecoder* audio_decoder = - AudioDecoder::Create(audio_codec_, audio_header_); - VideoDecoder* video_decoder = VideoDecoder::Create(video_codec_); - - if (!audio_decoder || !video_decoder) { - delete audio_decoder; - delete video_decoder; - UpdatePlayerState(kSbPlayerStateError); - return false; - } - - audio_renderer_ = new AudioRenderer(audio_decoder, audio_header_); - video_renderer_ = new VideoRenderer(video_decoder); - if (audio_renderer_->is_valid() && video_renderer_->is_valid()) { +bool PlayerWorker::DoInit() { + if (handler_->ProcessInitEvent()) { UpdatePlayerState(kSbPlayerStateInitialized); return true; } - delete audio_renderer_; - audio_renderer_ = NULL; - delete video_renderer_; - video_renderer_ = NULL; UpdatePlayerState(kSbPlayerStateError); return false; } -bool PlayerWorker::ProcessSeekEvent(const SeekEventData& data) { +bool PlayerWorker::DoSeek(const SeekEventData& data) { SB_DCHECK(player_state_ != kSbPlayerStateDestroyed); SB_DCHECK(player_state_ != kSbPlayerStateError); SB_DCHECK(ticket_ != data.ticket); @@ -160,106 +166,66 @@ SB_DLOG(INFO) << "Try to seek to timestamp " << data.seek_to_pts / kSbMediaTimeSecond; - SbMediaTime seek_to_pts = data.seek_to_pts; - if (seek_to_pts < 0) { - SB_DLOG(ERROR) << "Try to seek to negative timestamp " << seek_to_pts; - seek_to_pts = 0; + if (pending_audio_sample_.input_buffer != NULL) { + delete pending_audio_sample_.input_buffer; + pending_audio_sample_.input_buffer = NULL; + } + if (pending_video_sample_.input_buffer != NULL) { + delete pending_video_sample_.input_buffer; + pending_video_sample_.input_buffer = NULL; } - audio_renderer_->Pause(); - audio_decoder_state_ = kSbPlayerDecoderStateNeedsData; - video_decoder_state_ = kSbPlayerDecoderStateNeedsData; - audio_renderer_->Seek(seek_to_pts); - video_renderer_->Seek(seek_to_pts); + if (!handler_->ProcessSeekEvent(data)) { + return false; + } ticket_ = data.ticket; UpdatePlayerState(kSbPlayerStatePrerolling); - UpdateDecoderState(kSbMediaTypeAudio); - UpdateDecoderState(kSbMediaTypeVideo); + UpdateDecoderState(kSbMediaTypeAudio, kSbPlayerDecoderStateNeedsData); + UpdateDecoderState(kSbMediaTypeVideo, kSbPlayerDecoderStateNeedsData); + return true; } -bool PlayerWorker::ProcessWriteSampleEvent(const WriteSampleEventData& data, - bool* retry) { - SB_DCHECK(retry != NULL); +bool PlayerWorker::DoWriteSample(const WriteSampleEventData& data) { SB_DCHECK(player_state_ != kSbPlayerStateDestroyed); SB_DCHECK(player_state_ != kSbPlayerStateError); - *retry = false; - if (player_state_ == kSbPlayerStateInitialized || player_state_ == kSbPlayerStateEndOfStream || player_state_ == kSbPlayerStateError) { SB_LOG(ERROR) << "Try to write sample when |player_state_| is " << player_state_; + delete data.input_buffer; // Return true so the pipeline will continue running with the particular // call ignored. return true; } if (data.sample_type == kSbMediaTypeAudio) { - if (audio_renderer_->IsEndOfStreamWritten()) { - SB_LOG(WARNING) << "Try to write audio sample after EOS is reached"; + SB_DCHECK(pending_audio_sample_.input_buffer == NULL); + } else { + SB_DCHECK(pending_video_sample_.input_buffer == NULL); + } + bool written; + bool result = handler_->ProcessWriteSampleEvent(data, &written); + if (!written && result) { + if (data.sample_type == kSbMediaTypeAudio) { + pending_audio_sample_ = data; } else { - SB_DCHECK(audio_renderer_->CanAcceptMoreData()); - - if (data.input_buffer->drm_info()) { - if (!SbDrmSystemIsValid(drm_system_)) { - return false; - } - if (drm_system_->Decrypt(data.input_buffer) == - SbDrmSystemPrivate::kRetry) { - *retry = true; - return true; - } - } - audio_renderer_->WriteSample(*data.input_buffer); - if (audio_renderer_->CanAcceptMoreData()) { - audio_decoder_state_ = kSbPlayerDecoderStateNeedsData; - } else { - audio_decoder_state_ = kSbPlayerDecoderStateBufferFull; - } - UpdateDecoderState(kSbMediaTypeAudio); + pending_video_sample_ = data; } } else { - SB_DCHECK(data.sample_type == kSbMediaTypeVideo); - if (video_renderer_->IsEndOfStreamWritten()) { - SB_LOG(WARNING) << "Try to write video sample after EOS is reached"; - } else { - SB_DCHECK(video_renderer_->CanAcceptMoreData()); - if (data.input_buffer->drm_info()) { - if (!SbDrmSystemIsValid(drm_system_)) { - return false; - } - if (drm_system_->Decrypt(data.input_buffer) == - SbDrmSystemPrivate::kRetry) { - *retry = true; - return true; - } - } - video_renderer_->WriteSample(*data.input_buffer); - if (video_renderer_->CanAcceptMoreData()) { - video_decoder_state_ = kSbPlayerDecoderStateNeedsData; - } else { - video_decoder_state_ = kSbPlayerDecoderStateBufferFull; - } - UpdateDecoderState(kSbMediaTypeVideo); + delete data.input_buffer; + if (result) { + UpdateDecoderState(data.sample_type, kSbPlayerDecoderStateNeedsData); } } - - if (player_state_ == kSbPlayerStatePrerolling) { - if (!audio_renderer_->IsSeekingInProgress() && - !video_renderer_->IsSeekingInProgress()) { - UpdatePlayerState(kSbPlayerStatePresenting); - } - } - - return true; + return result; } -bool PlayerWorker::ProcessWriteEndOfStreamEvent( - const WriteEndOfStreamEventData& data) { +bool PlayerWorker::DoWriteEndOfStream(const WriteEndOfStreamEventData& data) { SB_DCHECK(player_state_ != kSbPlayerStateDestroyed); if (player_state_ == kSbPlayerStateInitialized || @@ -273,124 +239,47 @@ } if (data.stream_type == kSbMediaTypeAudio) { - if (audio_renderer_->IsEndOfStreamWritten()) { - SB_LOG(WARNING) << "Try to write audio EOS after EOS is enqueued"; - } else { - SB_LOG(INFO) << "Audio EOS enqueued"; - audio_renderer_->WriteEndOfStream(); - } - audio_decoder_state_ = kSbPlayerDecoderStateBufferFull; - UpdateDecoderState(kSbMediaTypeAudio); + SB_DCHECK(pending_audio_sample_.input_buffer == NULL); } else { - if (video_renderer_->IsEndOfStreamWritten()) { - SB_LOG(WARNING) << "Try to write video EOS after EOS is enqueued"; - } else { - SB_LOG(INFO) << "Video EOS enqueued"; - video_renderer_->WriteEndOfStream(); - } - video_decoder_state_ = kSbPlayerDecoderStateBufferFull; - UpdateDecoderState(kSbMediaTypeVideo); + SB_DCHECK(pending_video_sample_.input_buffer == NULL); } - if (player_state_ == kSbPlayerStatePrerolling) { - if (!audio_renderer_->IsSeekingInProgress() && - !video_renderer_->IsSeekingInProgress()) { - UpdatePlayerState(kSbPlayerStatePresenting); - } - } - - if (player_state_ == kSbPlayerStatePresenting) { - if (audio_renderer_->IsEndOfStreamPlayed() && - video_renderer_->IsEndOfStreamPlayed()) { - UpdatePlayerState(kSbPlayerStateEndOfStream); - } + if (!handler_->ProcessWriteEndOfStreamEvent(data)) { + return false; } return true; } -bool PlayerWorker::ProcessSetPauseEvent(const SetPauseEventData& data) { - // TODO: Check valid - paused_ = data.pause; - if (data.pause) { - audio_renderer_->Pause(); - SB_DLOG(INFO) << "Playback paused."; - } else { - audio_renderer_->Play(); - SB_DLOG(INFO) << "Playback started."; +bool PlayerWorker::DoUpdate(const SetBoundsEventData& data) { + if (pending_audio_sample_.input_buffer != NULL) { + if (!DoWriteSample(common::ResetAndReturn(&pending_audio_sample_))) { + return false; + } } - - return true; + if (pending_video_sample_.input_buffer != NULL) { + if (!DoWriteSample(common::ResetAndReturn(&pending_video_sample_))) { + return false; + } + } + return handler_->ProcessUpdateEvent(data); } -bool PlayerWorker::ProcessUpdateEvent(const SetBoundsEventData& bounds) { - SB_DCHECK(player_state_ != kSbPlayerStateDestroyed); +void PlayerWorker::DoStop() { + handler_->ProcessStopEvent(); - if (player_state_ == kSbPlayerStatePrerolling || - player_state_ == kSbPlayerStatePresenting) { - if (audio_renderer_->IsEndOfStreamPlayed() && - video_renderer_->IsEndOfStreamPlayed()) { - UpdatePlayerState(kSbPlayerStateEndOfStream); - } - - const VideoFrame& frame = - video_renderer_->GetCurrentFrame(audio_renderer_->GetCurrentTime()); -#if SB_IS(PLAYER_PUNCHED_OUT) - shared::starboard::Application::Get()->HandleFrame( - player_, frame, bounds.x, bounds.y, bounds.width, bounds.height); -#endif // SB_IS(PLAYER_PUNCHED_OUT) - - if (audio_decoder_state_ == kSbPlayerDecoderStateBufferFull && - audio_renderer_->CanAcceptMoreData()) { - audio_decoder_state_ = kSbPlayerDecoderStateNeedsData; - UpdateDecoderState(kSbMediaTypeAudio); - } - if (video_decoder_state_ == kSbPlayerDecoderStateBufferFull && - video_renderer_->CanAcceptMoreData()) { - video_decoder_state_ = kSbPlayerDecoderStateNeedsData; - UpdateDecoderState(kSbMediaTypeVideo); - } - - host_->UpdateMediaTime(audio_renderer_->GetCurrentTime(), ticket_); - } - - return true; -} - -void PlayerWorker::ProcessStopEvent() { - delete audio_renderer_; - audio_renderer_ = NULL; - delete video_renderer_; - video_renderer_ = NULL; -#if SB_IS(PLAYER_PUNCHED_OUT) - // Clear the video frame as we terminate. - shared::starboard::Application::Get()->HandleFrame(player_, VideoFrame(), 0, - 0, 0, 0); -#endif // SB_IS(PLAYER_PUNCHED_OUT) UpdatePlayerState(kSbPlayerStateDestroyed); } -void PlayerWorker::UpdateDecoderState(SbMediaType type) { +void PlayerWorker::UpdateDecoderState(SbMediaType type, + SbPlayerDecoderState state) { + SB_DCHECK(type == kSbMediaTypeAudio || type == kSbMediaTypeVideo); + if (!decoder_status_func_) { return; } - SB_DCHECK(type == kSbMediaTypeAudio || type == kSbMediaTypeVideo); - decoder_status_func_( - player_, context_, type, - type == kSbMediaTypeAudio ? audio_decoder_state_ : video_decoder_state_, - ticket_); -} -void PlayerWorker::UpdatePlayerState(SbPlayerState player_state) { - if (!player_status_func_) { - return; - } - - player_state_ = player_state; - if (player_state == kSbPlayerStatePresenting && !paused_) { - audio_renderer_->Play(); - } - player_status_func_(player_, context_, player_state_, ticket_); + decoder_status_func_(player_, context_, type, state, ticket_); } } // namespace player
diff --git a/src/starboard/shared/starboard/player/player_worker.h b/src/starboard/shared/starboard/player/player_worker.h index 843806f..d8bbb70 100644 --- a/src/starboard/shared/starboard/player/player_worker.h +++ b/src/starboard/shared/starboard/player/player_worker.h
@@ -15,13 +15,13 @@ #ifndef STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_WORKER_H_ #define STARBOARD_SHARED_STARBOARD_PLAYER_PLAYER_WORKER_H_ +#include "starboard/common/scoped_ptr.h" #include "starboard/log.h" #include "starboard/media.h" #include "starboard/player.h" #include "starboard/queue.h" #include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/player/audio_renderer_internal.h" -#include "starboard/shared/starboard/player/video_renderer_internal.h" +#include "starboard/shared/starboard/player/input_buffer_internal.h" #include "starboard/thread.h" #include "starboard/time.h" #include "starboard/window.h" @@ -31,6 +31,12 @@ namespace starboard { namespace player { +// This class creates a thread that executes events posted to an internally +// created queue. This guarantees that all such events are processed on the same +// thread. +// +// This class serves as the base class for platform specific PlayerWorkers so +// they needn't maintain the thread and queue internally. class PlayerWorker { public: class Host { @@ -78,6 +84,9 @@ kStop, }; + // TODO: No longer use a union so individual members can have non trivial + // ctor and WriteSampleEventData::input_buffer no longer has to be a + // pointer. union Data { SeekEventData seek; WriteSampleEventData write_sample; @@ -116,63 +125,90 @@ Data data; }; - static const SbTime kUpdateInterval = 5 * kSbTimeMillisecond; + class Handler { + public: + typedef void (PlayerWorker::*UpdateMediaTimeCB)(SbMediaTime media_time); + typedef SbPlayerState (PlayerWorker::*GetPlayerStateCB)() const; + typedef void (PlayerWorker::*UpdatePlayerStateCB)( + SbPlayerState player_state); + + typedef PlayerWorker::SeekEventData SeekEventData; + typedef PlayerWorker::WriteSampleEventData WriteSampleEventData; + typedef PlayerWorker::WriteEndOfStreamEventData WriteEndOfStreamEventData; + typedef PlayerWorker::SetPauseEventData SetPauseEventData; + typedef PlayerWorker::SetBoundsEventData SetBoundsEventData; + + virtual ~Handler() {} + + // This function will be called once inside PlayerWorker's ctor to setup the + // callbacks required by the Handler. + virtual void Setup(PlayerWorker* player_worker, + SbPlayer player, + UpdateMediaTimeCB update_media_time_cb, + GetPlayerStateCB get_player_state_cb, + UpdatePlayerStateCB update_player_state_cb) = 0; + + // All the Process* functions return false to signal a fatal error. The + // event processing loop in PlayerWorker will termimate in this case. + virtual bool ProcessInitEvent() = 0; + virtual bool ProcessSeekEvent(const SeekEventData& data) = 0; + virtual bool ProcessWriteSampleEvent(const WriteSampleEventData& data, + bool* written) = 0; + virtual bool ProcessWriteEndOfStreamEvent( + const WriteEndOfStreamEventData& data) = 0; + virtual bool ProcessSetPauseEvent(const SetPauseEventData& data) = 0; + virtual bool ProcessUpdateEvent(const SetBoundsEventData& data) = 0; + virtual void ProcessStopEvent() = 0; + }; PlayerWorker(Host* host, - SbWindow window, - SbMediaVideoCodec video_codec, - SbMediaAudioCodec audio_codec, - SbDrmSystem drm_system, - const SbMediaAudioHeader& audio_header, + scoped_ptr<Handler> handler, SbPlayerDecoderStatusFunc decoder_status_func, SbPlayerStatusFunc player_status_func, SbPlayer player, + SbTime update_interval, void* context); - ~PlayerWorker(); + virtual ~PlayerWorker(); template <typename EventData> void EnqueueEvent(const EventData& event_data) { + SB_DCHECK(SbThreadIsValid(thread_)); queue_.Put(new Event(event_data)); } private: + void UpdateMediaTime(SbMediaTime time); + + SbPlayerState player_state() const { return player_state_; } + void UpdatePlayerState(SbPlayerState player_state); + static void* ThreadEntryPoint(void* context); void RunLoop(); + bool DoInit(); + bool DoSeek(const SeekEventData& data); + bool DoWriteSample(const WriteSampleEventData& data); + bool DoWriteEndOfStream(const WriteEndOfStreamEventData& data); + bool DoUpdate(const SetBoundsEventData& data); + void DoStop(); - bool ProcessInitEvent(); - bool ProcessSeekEvent(const SeekEventData& data); - bool ProcessWriteSampleEvent(const WriteSampleEventData& data, bool* retry); - bool ProcessWriteEndOfStreamEvent(const WriteEndOfStreamEventData& data); - bool ProcessSetPauseEvent(const SetPauseEventData& data); - bool ProcessUpdateEvent(const SetBoundsEventData& bounds); - void ProcessStopEvent(); - - void UpdateDecoderState(SbMediaType type); - void UpdatePlayerState(SbPlayerState player_state); + void UpdateDecoderState(SbMediaType type, SbPlayerDecoderState state); SbThread thread_; Queue<Event*> queue_; Host* host_; + scoped_ptr<Handler> handler_; - SbWindow window_; - SbMediaVideoCodec video_codec_; - SbMediaAudioCodec audio_codec_; - SbDrmSystem drm_system_; - SbMediaAudioHeader audio_header_; SbPlayerDecoderStatusFunc decoder_status_func_; SbPlayerStatusFunc player_status_func_; SbPlayer player_; void* context_; - - AudioRenderer* audio_renderer_; - VideoRenderer* video_renderer_; - SbPlayerDecoderState audio_decoder_state_; - SbPlayerDecoderState video_decoder_state_; - - bool paused_; int ticket_; + SbPlayerState player_state_; + const SbTime update_interval_; + WriteSampleEventData pending_audio_sample_; + WriteSampleEventData pending_video_sample_; }; } // namespace player
diff --git a/src/starboard/shared/starboard/player/video_decoder_internal.h b/src/starboard/shared/starboard/player/video_decoder_internal.h deleted file mode 100644 index 444f049..0000000 --- a/src/starboard/shared/starboard/player/video_decoder_internal.h +++ /dev/null
@@ -1,72 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_DECODER_INTERNAL_H_ -#define STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_DECODER_INTERNAL_H_ - -#include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/player/input_buffer_internal.h" -#include "starboard/shared/starboard/player/video_frame_internal.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace player { - -// This class decodes encoded video stream into video frames. -class VideoDecoder { - public: - enum Status { kNeedMoreInput, kBufferFull, kFatalError }; - - class Host { - public: - // |frame| can contain a decoded frame or be NULL when |status| is not - // kFatalError. When status is kFatalError, |frame| will be NULL. Its - // user should only call WriteInputFrame() when |status| is kNeedMoreInput - // or when the instance is just created. Also note that calling Reset() or - // dtor from this callback will result in deadlock. - virtual void OnDecoderStatusUpdate(Status status, VideoFrame* frame) = 0; - - protected: - ~Host() {} - }; - - virtual ~VideoDecoder() {} - - virtual void SetHost(Host* host) = 0; - - // Send encoded video frame stored in |input_buffer| to decode. - virtual void WriteInputBuffer(const InputBuffer& input_buffer) = 0; - // Note that there won't be more input data unless Reset() is called. - // OnDecoderStatusUpdate will still be called on Host during flushing until - // the |frame| is an EOS frame. - virtual void WriteEndOfStream() = 0; - // Clear any cached buffer of the codec and reset the state of the codec. - // This function will be called during seek to ensure that there is no left - // over data from previous buffers. No DecoderStatusFunc call will be made - // after this function returns unless WriteInputFrame() or WriteEndOfStream() - // is called again. - virtual void Reset() = 0; - - // Individual implementation has to implement this function to create a video - // decoder. - static VideoDecoder* Create(SbMediaVideoCodec video_codec); -}; - -} // namespace player -} // namespace starboard -} // namespace shared -} // namespace starboard - -#endif // STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_DECODER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/player/video_frame_internal.cc b/src/starboard/shared/starboard/player/video_frame_internal.cc index f13dc3b..7bee2c4 100644 --- a/src/starboard/shared/starboard/player/video_frame_internal.cc +++ b/src/starboard/shared/starboard/player/video_frame_internal.cc
@@ -30,7 +30,7 @@ int s_u_to_g[256]; int s_v_to_g[256]; int s_u_to_b[256]; -uint8_t s_clamp_table[256 * 3]; +uint8_t s_clamp_table[256 * 5]; void EnsureYUVToRGBLookupTableInitialized() { if (s_yuv_to_rgb_lookup_table_initialized) { @@ -47,10 +47,13 @@ // // We optimize the conversion algorithm by creating two kinds of lookup // tables. The color component table contains pre-calculated color component - // values. The clamp table contains a map between |v| + 256 to the clamped + // values. The clamp table contains a map between |v| + 512 to the clamped // |v| to avoid conditional operation. - SbMemorySet(s_clamp_table, 0, 256); - SbMemorySet(s_clamp_table + 512, 0xff, 256); + // The minimum value of |v| can be 2.112f * (-128) = -271, the maximum value + // of |v| can be 1.164f * 255 + 2.112f * 127 = 565. So we need 512 bytes at + // each side of the clamp buffer. + SbMemorySet(s_clamp_table, 0, 512); + SbMemorySet(s_clamp_table + 768, 0xff, 512); for (int i = 0; i < 256; ++i) { s_y_to_rgb[i] = (static_cast<uint8_t>(i) - 16) * 1.164f; @@ -58,65 +61,88 @@ s_u_to_g[i] = (static_cast<uint8_t>(i) - 128) * -0.213; s_v_to_g[i] = (static_cast<uint8_t>(i) - 128) * -0.533f; s_u_to_b[i] = (static_cast<uint8_t>(i) - 128) * 2.112f; - s_clamp_table[256 + i] = i; + s_clamp_table[512 + i] = i; } s_yuv_to_rgb_lookup_table_initialized = true; } uint8_t ClampColorComponent(int component) { - return s_clamp_table[component + 256]; + return s_clamp_table[component + 512]; } } // namespace -VideoFrame::VideoFrame(const VideoFrame& that) { - *this = that; +VideoFrame::VideoFrame() { + InitializeToInvalidFrame(); } -VideoFrame& VideoFrame::operator=(const VideoFrame& that) { - this->format_ = that.format_; +VideoFrame::VideoFrame(int width, + int height, + SbMediaTime pts, + void* native_texture, + void* native_texture_context, + FreeNativeTextureFunc free_native_texture_func) { + SB_DCHECK(native_texture != NULL); + SB_DCHECK(free_native_texture_func != NULL); - this->pts_ = that.pts_; - this->planes_ = that.planes_; - this->pixel_buffer_ = that.pixel_buffer_; + InitializeToInvalidFrame(); - for (int i = 0; i < GetPlaneCount(); ++i) { - const uint8_t* data = that.GetPlane(i).data; - const uint8_t* base = &that.pixel_buffer_[0]; - ptrdiff_t offset = data - base; - SB_DCHECK(offset >= 0); - SB_DCHECK(offset < static_cast<ptrdiff_t>(that.pixel_buffer_.size())); - planes_[i].data = &pixel_buffer_[0] + offset; + format_ = kNativeTexture; + width_ = width; + height_ = height; + pts_ = pts; + native_texture_ = native_texture; + native_texture_context_ = native_texture_context; + free_native_texture_func_ = free_native_texture_func; +} + +VideoFrame::~VideoFrame() { + if (format_ == kNativeTexture) { + free_native_texture_func_(native_texture_context_, native_texture_); } +} - return *this; +int VideoFrame::GetPlaneCount() const { + SB_DCHECK(format_ != kInvalid); + SB_DCHECK(format_ != kNativeTexture); + + return static_cast<int>(planes_.size()); } const VideoFrame::Plane& VideoFrame::GetPlane(int index) const { + SB_DCHECK(format_ != kInvalid); + SB_DCHECK(format_ != kNativeTexture); SB_DCHECK(index >= 0 && index < GetPlaneCount()) << "Invalid index: " << index; return planes_[index]; } -VideoFrame VideoFrame::ConvertTo(Format target_format) const { +void* VideoFrame::native_texture() const { + SB_DCHECK(format_ == kNativeTexture); + return native_texture_; +} + +scoped_refptr<VideoFrame> VideoFrame::ConvertTo(Format target_format) const { SB_DCHECK(format_ == kYV12); SB_DCHECK(target_format == kBGRA32); EnsureYUVToRGBLookupTableInitialized(); - VideoFrame target_frame; + scoped_refptr<VideoFrame> target_frame(new VideoFrame); - target_frame.format_ = target_format; - target_frame.pts_ = pts_; - target_frame.pixel_buffer_.resize(width() * height() * 4); - target_frame.planes_.push_back( - Plane(width(), height(), width() * 4, &target_frame.pixel_buffer_[0])); + target_frame->format_ = target_format; + target_frame->width_ = width(); + target_frame->height_ = height(); + target_frame->pts_ = pts_; + target_frame->pixel_buffer_.reset(new uint8_t[width() * height() * 4]); + target_frame->planes_.push_back( + Plane(width(), height(), width() * 4, target_frame->pixel_buffer_.get())); const uint8_t* y_data = GetPlane(0).data; const uint8_t* u_data = GetPlane(1).data; const uint8_t* v_data = GetPlane(2).data; - uint8_t* bgra_data = &target_frame.pixel_buffer_[0]; + uint8_t* bgra_data = target_frame->pixel_buffer_.get(); int height = this->height(); int width = this->width(); @@ -155,21 +181,23 @@ } // static -VideoFrame VideoFrame::CreateEOSFrame() { - return VideoFrame(); +scoped_refptr<VideoFrame> VideoFrame::CreateEOSFrame() { + return new VideoFrame; } // static -VideoFrame VideoFrame::CreateYV12Frame(int width, - int height, - int pitch_in_bytes, - SbMediaTime pts, - const uint8_t* y, - const uint8_t* u, - const uint8_t* v) { - VideoFrame frame; - frame.format_ = kYV12; - frame.pts_ = pts; +scoped_refptr<VideoFrame> VideoFrame::CreateYV12Frame(int width, + int height, + int pitch_in_bytes, + SbMediaTime pts, + const uint8_t* y, + const uint8_t* u, + const uint8_t* v) { + scoped_refptr<VideoFrame> frame(new VideoFrame); + frame->format_ = kYV12; + frame->width_ = width; + frame->height_ = height; + frame->pts_ = pts; // U/V planes generally have half resolution of the Y plane. However, in the // extreme case that any dimension of Y plane is odd, we want to have an @@ -180,24 +208,38 @@ int y_plane_size_in_bytes = height * pitch_in_bytes; int uv_plane_size_in_bytes = uv_height * uv_pitch_in_bytes; - frame.pixel_buffer_.assign(y, y + y_plane_size_in_bytes); - frame.pixel_buffer_.insert(frame.pixel_buffer_.end(), u, - u + uv_plane_size_in_bytes); - frame.pixel_buffer_.insert(frame.pixel_buffer_.end(), v, - v + uv_plane_size_in_bytes); + frame->pixel_buffer_.reset( + new uint8_t[y_plane_size_in_bytes + uv_plane_size_in_bytes * 2]); + SbMemoryCopy(frame->pixel_buffer_.get(), y, y_plane_size_in_bytes); + SbMemoryCopy(frame->pixel_buffer_.get() + y_plane_size_in_bytes, u, + uv_plane_size_in_bytes); + SbMemoryCopy(frame->pixel_buffer_.get() + y_plane_size_in_bytes + + uv_plane_size_in_bytes, + v, uv_plane_size_in_bytes); - frame.planes_.push_back( - Plane(width, height, pitch_in_bytes, &frame.pixel_buffer_[0])); - frame.planes_.push_back( + frame->planes_.push_back( + Plane(width, height, pitch_in_bytes, frame->pixel_buffer_.get())); + frame->planes_.push_back( Plane(uv_width, uv_height, uv_pitch_in_bytes, - &frame.pixel_buffer_[0] + y_plane_size_in_bytes)); - frame.planes_.push_back(Plane(uv_width, uv_height, uv_pitch_in_bytes, - &frame.pixel_buffer_[0] + - y_plane_size_in_bytes + - uv_plane_size_in_bytes)); + frame->pixel_buffer_.get() + y_plane_size_in_bytes)); + frame->planes_.push_back(Plane(uv_width, uv_height, uv_pitch_in_bytes, + frame->pixel_buffer_.get() + + y_plane_size_in_bytes + + uv_plane_size_in_bytes)); return frame; } +void VideoFrame::InitializeToInvalidFrame() { + format_ = kInvalid; + width_ = 0; + height_ = 0; + + pts_ = 0; + native_texture_ = NULL; + native_texture_context_ = NULL; + free_native_texture_func_ = NULL; +} + } // namespace player } // namespace starboard } // namespace shared
diff --git a/src/starboard/shared/starboard/player/video_frame_internal.h b/src/starboard/shared/starboard/player/video_frame_internal.h index 745ec31..c956e90 100644 --- a/src/starboard/shared/starboard/player/video_frame_internal.h +++ b/src/starboard/shared/starboard/player/video_frame_internal.h
@@ -17,6 +17,9 @@ #include <vector> +#include "starboard/common/ref_counted.h" +#include "starboard/common/scoped_ptr.h" +#include "starboard/configuration.h" #include "starboard/media.h" #include "starboard/shared/internal_only.h" @@ -26,10 +29,12 @@ namespace player { // A video frame produced by a video decoder. -class VideoFrame { +class VideoFrame : public RefCountedThreadSafe<VideoFrame> { public: + typedef void (*FreeNativeTextureFunc)(void* context, void* textue); + enum Format { - kInvalid, + kInvalid, // A VideoFrame in this format can be used to indicate EOS. // This is the native format supported by XComposite (PictStandardARGB32 // with bytes swapped). Remove this once we are able to pass out frames // as YV12 textures. @@ -50,37 +55,55 @@ const uint8_t* data; }; - VideoFrame() : format_(kInvalid) {} - VideoFrame(const VideoFrame& that); - - VideoFrame& operator=(const VideoFrame& that); + VideoFrame(); // Create an EOS frame. + VideoFrame(int width, + int height, + SbMediaTime pts, + void* native_texture, + void* native_texture_context, + FreeNativeTextureFunc free_native_texture_func); + ~VideoFrame(); Format format() const { return format_; } - int width() const { return GetPlaneCount() == 0 ? 0 : GetPlane(0).width; } - int height() const { return GetPlaneCount() == 0 ? 0 : GetPlane(0).height; } - bool IsEndOfStream() const { return format_ == kInvalid; } SbMediaTime pts() const { return pts_; } - int GetPlaneCount() const { return static_cast<int>(planes_.size()); } + int width() const { return width_; } + int height() const { return height_; } + + int GetPlaneCount() const; const Plane& GetPlane(int index) const; - VideoFrame ConvertTo(Format target_format) const; + void* native_texture() const; - static VideoFrame CreateEOSFrame(); - static VideoFrame CreateYV12Frame(int width, - int height, - int pitch_in_bytes, - SbMediaTime pts, - const uint8_t* y, - const uint8_t* u, - const uint8_t* v); + scoped_refptr<VideoFrame> ConvertTo(Format target_format) const; + + static scoped_refptr<VideoFrame> CreateEOSFrame(); + static scoped_refptr<VideoFrame> CreateYV12Frame(int width, + int height, + int pitch_in_bytes, + SbMediaTime pts, + const uint8_t* y, + const uint8_t* u, + const uint8_t* v); private: - Format format_; + void InitializeToInvalidFrame(); + Format format_; + int width_; + int height_; SbMediaTime pts_; + + // The following two variables are valid when the frame contains pixel data. std::vector<Plane> planes_; - std::vector<uint8_t> pixel_buffer_; + scoped_array<uint8_t> pixel_buffer_; + + // The following three variables are valid when |format_| is `kNativeTexture`. + void* native_texture_; + void* native_texture_context_; + FreeNativeTextureFunc free_native_texture_func_; + + SB_DISALLOW_COPY_AND_ASSIGN(VideoFrame); }; } // namespace player
diff --git a/src/starboard/shared/starboard/player/video_renderer_internal.cc b/src/starboard/shared/starboard/player/video_renderer_internal.cc deleted file mode 100644 index da9ccf8..0000000 --- a/src/starboard/shared/starboard/player/video_renderer_internal.cc +++ /dev/null
@@ -1,136 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "starboard/shared/starboard/player/video_renderer_internal.h" - -#include <algorithm> - -namespace starboard { -namespace shared { -namespace starboard { -namespace player { - -VideoRenderer::VideoRenderer(VideoDecoder* decoder) - : seeking_(false), - seeking_to_pts_(0), - end_of_stream_written_(false), - need_more_input_(true), - decoder_(decoder) { - SB_DCHECK(decoder_ != NULL); - decoder_->SetHost(this); -} - -VideoRenderer::~VideoRenderer() { - delete decoder_; -} - -void VideoRenderer::WriteSample(const InputBuffer& input_buffer) { - ScopedLock lock(mutex_); - - if (end_of_stream_written_) { - SB_LOG(ERROR) << "Appending video sample at " << input_buffer.pts() - << " after EOS reached."; - return; - } - need_more_input_ = false; - decoder_->WriteInputBuffer(input_buffer); -} - -void VideoRenderer::WriteEndOfStream() { - ScopedLock lock(mutex_); - - SB_LOG_IF(WARNING, end_of_stream_written_) - << "Try to write EOS after EOS is reached"; - if (end_of_stream_written_) { - return; - } - end_of_stream_written_ = true; - decoder_->WriteEndOfStream(); -} - -void VideoRenderer::Seek(SbMediaTime seek_to_pts) { - SB_DCHECK(seek_to_pts >= 0); - - decoder_->Reset(); - - ScopedLock lock(mutex_); - - seeking_to_pts_ = std::max<SbMediaTime>(seek_to_pts, 0); - seeking_ = true; - end_of_stream_written_ = false; - - frames_.clear(); -} - -const VideoFrame& VideoRenderer::GetCurrentFrame(SbMediaTime media_time) { - ScopedLock lock(mutex_); - - if (frames_.empty()) { - // We cannot paint anything if there is no frames. - return empty_frame_; - } - // Remove any frames with timestamps earlier than |media_time|, but always - // keep at least one of the frames. - while (frames_.size() > 1 && frames_.front().pts() < media_time) { - frames_.pop_front(); - } - - return frames_.front(); -} - -bool VideoRenderer::IsEndOfStreamPlayed() const { - ScopedLock lock(mutex_); - return end_of_stream_written_ && frames_.size() <= 1; -} - -bool VideoRenderer::CanAcceptMoreData() const { - ScopedLock lock(mutex_); - return frames_.size() < kMaxCachedFrames && !end_of_stream_written_ && - need_more_input_; -} - -bool VideoRenderer::IsSeekingInProgress() const { - ScopedLock lock(mutex_); - return seeking_; -} - -void VideoRenderer::OnDecoderStatusUpdate(VideoDecoder::Status status, - VideoFrame* frame) { - ScopedLock lock(mutex_); - - if (frame) { - bool frame_too_early = false; - if (seeking_) { - if (frame->IsEndOfStream()) { - seeking_ = false; - } else if (frame->pts() < seeking_to_pts_) { - frame_too_early = true; - } - } - if (!frame_too_early) { - frames_.push_back(*frame); - } - - if (seeking_ && frames_.size() > kPrerollFrames) { - seeking_ = false; - } - } - - need_more_input_ = (status == VideoDecoder::kNeedMoreInput); -} - -} // namespace player -} // namespace starboard -} // namespace shared -} // namespace starboard
diff --git a/src/starboard/shared/starboard/player/video_renderer_internal.h b/src/starboard/shared/starboard/player/video_renderer_internal.h deleted file mode 100644 index f62cfda..0000000 --- a/src/starboard/shared/starboard/player/video_renderer_internal.h +++ /dev/null
@@ -1,87 +0,0 @@ -// Copyright 2016 Google Inc. All Rights Reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_RENDERER_INTERNAL_H_ -#define STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_RENDERER_INTERNAL_H_ - -#include <list> - -#include "starboard/log.h" -#include "starboard/media.h" -#include "starboard/mutex.h" -#include "starboard/shared/internal_only.h" -#include "starboard/shared/starboard/player/input_buffer_internal.h" -#include "starboard/shared/starboard/player/video_decoder_internal.h" -#include "starboard/shared/starboard/player/video_frame_internal.h" - -namespace starboard { -namespace shared { -namespace starboard { -namespace player { - -class VideoRenderer : private VideoDecoder::Host { - public: - explicit VideoRenderer(VideoDecoder* decoder); - ~VideoRenderer(); - - bool is_valid() const { return true; } - - void WriteSample(const InputBuffer& input_buffer); - void WriteEndOfStream(); - - void Seek(SbMediaTime seek_to_pts); - - const VideoFrame& GetCurrentFrame(SbMediaTime media_time); - - bool IsEndOfStreamWritten() const { return end_of_stream_written_; } - bool IsEndOfStreamPlayed() const; - bool CanAcceptMoreData() const; - bool IsSeekingInProgress() const; - - private: - typedef std::list<VideoFrame> Frames; - - // Preroll considered finished after either kPrerollFrames is cached or EOS - // is reached. - static const size_t kPrerollFrames = 1; - // Set a soft limit for the max video frames we can cache so we can: - // 1. Avoid using too much memory. - // 2. Have the frame cache full to simulate the state that the renderer can - // no longer accept more data. - static const size_t kMaxCachedFrames = 8; - - // VideoDecoder::Host method. - void OnDecoderStatusUpdate(VideoDecoder::Status status, - VideoFrame* frame) SB_OVERRIDE; - - ::starboard::Mutex mutex_; - - bool seeking_; - Frames frames_; - - VideoFrame empty_frame_; - - SbMediaTime seeking_to_pts_; - bool end_of_stream_written_; - bool need_more_input_; - - VideoDecoder* decoder_; -}; - -} // namespace player -} // namespace starboard -} // namespace shared -} // namespace starboard - -#endif // STARBOARD_SHARED_STARBOARD_PLAYER_VIDEO_RENDERER_INTERNAL_H_
diff --git a/src/starboard/shared/starboard/shared_main_adapter.cc b/src/starboard/shared/starboard/shared_main_adapter.cc new file mode 100644 index 0000000..2a6a911 --- /dev/null +++ b/src/starboard/shared/starboard/shared_main_adapter.cc
@@ -0,0 +1,31 @@ +// Copyright 2016 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. + +// Imports and calls into StarboardMain() from main(). Can be used to +// link into a shared Starboard executable to turn it into a traditional +// executable. + +#include "starboard/configuration.h" + +#undef main + +extern "C" { + +SB_IMPORT_PLATFORM int StarboardMain(int argc, char** argv); + +int main(int argc, char** argv) { + return StarboardMain(argc, argv); +} + +} // extern "C"
diff --git a/src/starboard/shared/stub/decode_target_create_blitter.cc b/src/starboard/shared/stub/decode_target_create_blitter.cc new file mode 100644 index 0000000..019658d --- /dev/null +++ b/src/starboard/shared/stub/decode_target_create_blitter.cc
@@ -0,0 +1,20 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/decode_target.h" + +SbDecodeTarget SbDecodeTargetCreate(SbDecodeTargetFormat /*format*/, + SbBlitterSurface* /*planes*/) { + return kSbDecodeTargetInvalid; +}
diff --git a/src/starboard/shared/stub/decode_target_create_egl.cc b/src/starboard/shared/stub/decode_target_create_egl.cc new file mode 100644 index 0000000..d43b91b --- /dev/null +++ b/src/starboard/shared/stub/decode_target_create_egl.cc
@@ -0,0 +1,22 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/decode_target.h" + +SbDecodeTarget SbDecodeTargetCreate(EGLDisplay /*display*/, + EGLContext /*context*/, + SbDecodeTargetFormat /*format*/, + GLuint* /*planes*/) { + return kSbDecodeTargetInvalid; +}
diff --git a/src/starboard/shared/stub/decode_target_destroy.cc b/src/starboard/shared/stub/decode_target_destroy.cc new file mode 100644 index 0000000..6762123 --- /dev/null +++ b/src/starboard/shared/stub/decode_target_destroy.cc
@@ -0,0 +1,17 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "starboard/decode_target.h" + +void SbDecodeTargetDestroy(SbDecodeTarget /*decode_target*/) {}
diff --git a/src/starboard/shared/stub/decode_target_get_format.cc b/src/starboard/shared/stub/decode_target_get_format.cc new file mode 100644 index 0000000..e8f9ea7 --- /dev/null +++ b/src/starboard/shared/stub/decode_target_get_format.cc
@@ -0,0 +1,19 @@ +// Copyright 2016 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 req