diff options
-rw-r--r-- | media/blink/BUILD.gn | 5 | ||||
-rw-r--r-- | media/blink/cache_util.cc | 38 | ||||
-rw-r--r-- | media/blink/cache_util.h | 8 | ||||
-rw-r--r-- | media/blink/media_blink.gyp | 6 | ||||
-rw-r--r-- | media/blink/multibuffer.cc | 40 | ||||
-rw-r--r-- | media/blink/multibuffer.h | 38 | ||||
-rw-r--r-- | media/blink/multibuffer_unittest.cc | 104 | ||||
-rw-r--r-- | media/blink/resource_multibuffer_data_provider.cc | 480 | ||||
-rw-r--r-- | media/blink/resource_multibuffer_data_provider.h | 126 | ||||
-rw-r--r-- | media/blink/resource_multibuffer_data_provider_unittest.cc | 330 | ||||
-rw-r--r-- | media/blink/url_index.cc | 237 | ||||
-rw-r--r-- | media/blink/url_index.h | 241 | ||||
-rw-r--r-- | media/blink/url_index_unittest.cc | 156 |
13 files changed, 1723 insertions, 86 deletions
diff --git a/media/blink/BUILD.gn b/media/blink/BUILD.gn index 3e97448..9eff8c1 100644 --- a/media/blink/BUILD.gn +++ b/media/blink/BUILD.gn @@ -52,8 +52,12 @@ component("blink") { "multibuffer_reader.h", "new_session_cdm_result_promise.cc", "new_session_cdm_result_promise.h", + "resource_multibuffer_data_provider.cc", + "resource_multibuffer_data_provider.h", "texttrack_impl.cc", "texttrack_impl.h", + "url_index.cc", + "url_index.h", "video_frame_compositor.cc", "video_frame_compositor.h", "webaudiosourceprovider_impl.cc", @@ -129,6 +133,7 @@ test("media_blink_unittests") { "test_random.h", "test_response_generator.cc", "test_response_generator.h", + "url_index_unittest.cc", "video_frame_compositor_unittest.cc", "webaudiosourceprovider_impl_unittest.cc", ] diff --git a/media/blink/cache_util.cc b/media/blink/cache_util.cc index e8cb563..0f55b1f 100644 --- a/media/blink/cache_util.cc +++ b/media/blink/cache_util.cc @@ -88,4 +88,42 @@ uint32 GetReasonsForUncacheability(const WebURLResponse& response) { return reasons; } +base::TimeDelta GetCacheValidUntil(const WebURLResponse& response) { + std::string cache_control_header = + base::ToLowerASCII(response.httpHeaderField("cache-control").utf8()); + if (cache_control_header.find("no-cache") != std::string::npos) + return base::TimeDelta(); + if (cache_control_header.find("must-revalidate") != std::string::npos) + return base::TimeDelta(); + + // Max cache timeout ~= 1 month. + base::TimeDelta ret = base::TimeDelta::FromDays(30); + + const char kMaxAgePrefix[] = "max-age="; + const size_t kMaxAgePrefixLen = arraysize(kMaxAgePrefix) - 1; + if (cache_control_header.substr(0, kMaxAgePrefixLen) == kMaxAgePrefix) { + int64 max_age_seconds; + base::StringToInt64( + base::StringPiece(cache_control_header.begin() + kMaxAgePrefixLen, + cache_control_header.end()), + &max_age_seconds); + + ret = std::min(ret, TimeDelta::FromSeconds(max_age_seconds)); + } else { + // Note that |date| may be smaller than |expires|, which means we'll + // return a timetick some time in the past. + Time date; + Time expires; + if (Time::FromString(response.httpHeaderField("Date").utf8().data(), + &date) && + Time::FromString(response.httpHeaderField("Expires").utf8().data(), + &expires) && + date > Time() && expires > Time()) { + ret = std::min(ret, expires - date); + } + } + + return ret; +} + } // namespace media diff --git a/media/blink/cache_util.h b/media/blink/cache_util.h index 033b6bd..8dd2a19 100644 --- a/media/blink/cache_util.h +++ b/media/blink/cache_util.h @@ -8,6 +8,7 @@ #include <vector> #include "base/basictypes.h" +#include "base/time/time.h" #include "media/blink/media_blink_export.h" namespace blink { @@ -36,6 +37,13 @@ enum UncacheableReason { uint32 MEDIA_BLINK_EXPORT GetReasonsForUncacheability(const blink::WebURLResponse& response); +// Returns when we should evict data from this response from our +// memory cache. Note that we may still cache data longer if +// a audio/video tag is currently using it. Returns a TimeDelta +// which is should be added to base::Time::Now() or base::TimeTicks::Now(). +base::TimeDelta MEDIA_BLINK_EXPORT +GetCacheValidUntil(const blink::WebURLResponse& response); + } // namespace media #endif // MEDIA_BLINK_CACHE_UTIL_H_ diff --git a/media/blink/media_blink.gyp b/media/blink/media_blink.gyp index 3b08ee7..2617fb5 100644 --- a/media/blink/media_blink.gyp +++ b/media/blink/media_blink.gyp @@ -57,8 +57,12 @@ 'multibuffer_reader.h', 'new_session_cdm_result_promise.cc', 'new_session_cdm_result_promise.h', + 'resource_multibuffer_data_provider.cc', + 'resource_multibuffer_data_provider.h', 'texttrack_impl.cc', 'texttrack_impl.h', + 'url_index.cc', + 'url_index.h', 'video_frame_compositor.cc', 'video_frame_compositor.h', 'webaudiosourceprovider_impl.cc', @@ -131,10 +135,12 @@ 'mock_weburlloader.cc', 'mock_weburlloader.h', 'multibuffer_unittest.cc', + 'resource_multibuffer_data_provider_unittest.cc', 'run_all_unittests.cc', 'test_random.h', 'test_response_generator.cc', 'test_response_generator.h', + 'url_index_unittest.cc', 'video_frame_compositor_unittest.cc', 'webaudiosourceprovider_impl_unittest.cc', ], diff --git a/media/blink/multibuffer.cc b/media/blink/multibuffer.cc index 41c3582..a8d981e 100644 --- a/media/blink/multibuffer.cc +++ b/media/blink/multibuffer.cc @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <utility> + #include "media/blink/multibuffer.h" #include "base/bind.h" @@ -113,10 +115,6 @@ MultiBuffer::MultiBuffer(int32_t block_size_shift, : max_size_(0), block_size_shift_(block_size_shift), lru_(global_lru) {} MultiBuffer::~MultiBuffer() { - // Delete all writers. - for (const auto& i : writer_index_) { - delete i.second; - } // Remove all blocks from the LRU. for (const auto& i : data_) { lru_->Remove(this, i.first); @@ -155,15 +153,14 @@ void MultiBuffer::AddReader(const BlockId& pos, Reader* reader) { // Make sure that there are no present blocks between the writer and // the requested position, as that will cause the writer to quit. if (closest_writer > closest_block) { - provider = writer_index_[closest_writer]; + provider = writer_index_[closest_writer].get(); DCHECK(provider); } } if (!provider) { DCHECK(writer_index_.find(pos) == writer_index_.end()); - provider = writer_index_[pos] = CreateWriter(pos); - provider->SetAvailableCallback(base::Bind( - &MultiBuffer::DataProviderEvent, base::Unretained(this), provider)); + writer_index_[pos] = CreateWriter(pos); + provider = writer_index_[pos].get(); } provider->SetDeferred(false); } @@ -183,7 +180,7 @@ void MultiBuffer::CleanupWriters(const BlockId& pos) { BlockId closest_writer = ClosestPreviousEntry(writer_index_, p2); while (closest_writer > pos - kMaxWaitForWriterOffset) { DCHECK(writer_index_[closest_writer]); - DataProviderEvent(writer_index_[closest_writer]); + OnDataProviderEvent(writer_index_[closest_writer].get()); closest_writer = ClosestPreviousEntry(writer_index_, closest_writer - 1); } } @@ -258,25 +255,28 @@ void MultiBuffer::ReleaseBlocks(const std::vector<MultiBufferBlockId>& blocks) { } } } + if (data_.empty()) + OnEmpty(); } +void MultiBuffer::OnEmpty() {} + void MultiBuffer::AddProvider(scoped_ptr<DataProvider> provider) { // If there is already a provider in the same location, we delete it. DCHECK(!provider->Available()); BlockId pos = provider->Tell(); - DataProvider** place = &writer_index_[pos]; - DCHECK_NE(*place, provider.get()); - if (*place) - delete *place; - *place = provider.release(); + writer_index_[pos] = std::move(provider); } scoped_ptr<MultiBuffer::DataProvider> MultiBuffer::RemoveProvider( DataProvider* provider) { BlockId pos = provider->Tell(); - DCHECK_EQ(writer_index_[pos], provider); - writer_index_.erase(pos); - return scoped_ptr<DataProvider>(provider); + auto iter = writer_index_.find(pos); + DCHECK(iter != writer_index_.end()); + DCHECK_EQ(iter->second.get(), provider); + scoped_ptr<DataProvider> ret = std::move(iter->second); + writer_index_.erase(iter); + return ret; } MultiBuffer::ProviderState MultiBuffer::SuggestProviderState( @@ -324,7 +324,7 @@ void MultiBuffer::Prune(size_t max_to_free) { lru_->Prune(max_to_free); } -void MultiBuffer::DataProviderEvent(DataProvider* provider_tmp) { +void MultiBuffer::OnDataProviderEvent(DataProvider* provider_tmp) { scoped_ptr<DataProvider> provider(RemoveProvider(provider_tmp)); BlockId start_pos = provider->Tell(); BlockId pos = start_pos; @@ -333,7 +333,7 @@ void MultiBuffer::DataProviderEvent(DataProvider* provider_tmp) { while (!ProviderCollision(pos) && !eof) { if (!provider->Available()) { - AddProvider(provider.Pass()); + AddProvider(std::move(provider)); break; } DCHECK_GE(pos, 0); @@ -361,7 +361,7 @@ void MultiBuffer::DataProviderEvent(DataProvider* provider_tmp) { // Even if we did call AddProvider, calling NotifyAvailableRange can cause // readers to seek or self-destruct and clean up any associated writers. auto i = writer_index_.find(pos); - if (i != writer_index_.end() && i->second == provider_tmp) { + if (i != writer_index_.end() && i->second.get() == provider_tmp) { switch (SuggestProviderState(pos)) { case ProviderStateLoad: // Not sure we actually need to do this diff --git a/media/blink/multibuffer.h b/media/blink/multibuffer.h index d469d43..eb3530d 100644 --- a/media/blink/multibuffer.h +++ b/media/blink/multibuffer.h @@ -16,6 +16,7 @@ #include "base/containers/hash_tables.h" #include "base/macros.h" #include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "media/base/data_buffer.h" #include "media/blink/interval_map.h" #include "media/blink/lru.h" @@ -23,8 +24,14 @@ namespace media { +// Used to identify a block of data in the multibuffer. +// Our blocks are 32kb (1 << 15), so our maximum cacheable file size +// is 1 << (15 + 31) = 64Tb typedef int32_t MultiBufferBlockId; class MultiBuffer; + +// This type is used to identify a block in the LRU, which is shared between +// multibuffers. typedef std::pair<MultiBuffer*, MultiBufferBlockId> MultiBufferGlobalBlockId; } // namespace media @@ -72,8 +79,6 @@ const int kMaxWaitForWriterOffset = 5; // This is the size of the look-behind region. const int kMaxWaitForReaderOffset = 50; -class MultiBuffer; - // MultiBuffers are multi-reader multi-writer cache/buffers with // prefetching and pinning. Data is stored internally in ref-counted // blocks of identical size. |block_size_shift| is log2 of the block @@ -119,9 +124,6 @@ class MEDIA_BLINK_EXPORT MultiBuffer { // DataBuffer. virtual scoped_refptr<DataBuffer> Read() = 0; - // |cb| is called every time Available() becomes true. - virtual void SetAvailableCallback(const base::Closure& cb) = 0; - // Ask the data provider to stop giving us data. // It's ok if the effect is not immediate. virtual void SetDeferred(bool deferred) = 0; @@ -229,8 +231,8 @@ class MEDIA_BLINK_EXPORT MultiBuffer { // not call it anymore. scoped_ptr<DataProvider> RemoveProvider(DataProvider* provider); - // Add a writer to this cache. Cache takes ownership and - // may choose to destroy it. + // Add a writer to this cache. Cache takes ownership, and may + // destroy |provider| later. (Not during this call.) void AddProvider(scoped_ptr<DataProvider> provider); // Transfer all data from |other| to this. @@ -240,13 +242,23 @@ class MEDIA_BLINK_EXPORT MultiBuffer { const DataMap& map() const { return data_; } int32_t block_size_shift() const { return block_size_shift_; } + // Callback which notifies us that a data provider has + // some data for us. Also called when it might be appropriate + // for a provider in a deferred state to wake up. + void OnDataProviderEvent(DataProvider* provider); + protected: // Create a new writer at |pos| and return it. // Users needs to implemement this method. - virtual DataProvider* CreateWriter(const BlockId& pos) = 0; + virtual scoped_ptr<DataProvider> CreateWriter(const BlockId& pos) = 0; virtual bool RangeSupported() const = 0; + // Called when the cache becomes empty. Implementations can use this + // as a signal for when we should free this object and any metadata + // that goes with it. + virtual void OnEmpty(); + private: // For testing. friend class TestMultiBuffer; @@ -276,11 +288,6 @@ class MEDIA_BLINK_EXPORT MultiBuffer { void NotifyAvailableRange(const Interval<MultiBufferBlockId>& observer_range, const Interval<MultiBufferBlockId>& new_range); - // Callback which notifies us that a data provider has - // some data for us. Also called when it might be apprperiate - // for a provider in a deferred state to wake up. - void DataProviderEvent(DataProvider* provider); - // Max number of blocks. int64_t max_size_; @@ -295,8 +302,7 @@ class MEDIA_BLINK_EXPORT MultiBuffer { // Keeps track of writers by their position. // The writers are owned by this class. - // TODO(hubbe): Use ScopedPtrMap here. (must add upper/lower_bound first) - std::map<BlockId, DataProvider*> writer_index_; + std::map<BlockId, scoped_ptr<DataProvider>> writer_index_; // Gloabally shared LRU, decides which block to free next. scoped_refptr<GlobalLRU> lru_; @@ -310,6 +316,8 @@ class MEDIA_BLINK_EXPORT MultiBuffer { // and 0 for all blocks that are not. Used to quickly figure out // ranges of available/unavailable blocks without iterating. IntervalMap<BlockId, int32_t> present_; + + DISALLOW_COPY_AND_ASSIGN(MultiBuffer); }; } // namespace media diff --git a/media/blink/multibuffer_unittest.cc b/media/blink/multibuffer_unittest.cc index 96c8c07..4a2a64b 100644 --- a/media/blink/multibuffer_unittest.cc +++ b/media/blink/multibuffer_unittest.cc @@ -18,27 +18,32 @@ const int kBlockSizeShift = 8; const size_t kBlockSize = 1UL << kBlockSizeShift; namespace media { -class TestMultiBufferDataProvider; -std::vector<TestMultiBufferDataProvider*> writers; +class FakeMultiBufferDataProvider; -class TestMultiBufferDataProvider : public media::MultiBuffer::DataProvider { +namespace { +std::vector<FakeMultiBufferDataProvider*> writers; +} // namespace + +class FakeMultiBufferDataProvider : public MultiBuffer::DataProvider { public: - TestMultiBufferDataProvider(MultiBufferBlockId pos, + FakeMultiBufferDataProvider(MultiBufferBlockId pos, size_t file_size, int max_blocks_after_defer, bool must_read_whole_file, - media::TestRandom* rnd) + MultiBuffer* multibuffer, + TestRandom* rnd) : pos_(pos), blocks_until_deferred_(1 << 30), max_blocks_after_defer_(max_blocks_after_defer), file_size_(file_size), must_read_whole_file_(must_read_whole_file), + multibuffer_(multibuffer), rnd_(rnd) { writers.push_back(this); } - ~TestMultiBufferDataProvider() override { + ~FakeMultiBufferDataProvider() override { if (must_read_whole_file_) { CHECK_GE(pos_ * kBlockSize, file_size_); } @@ -64,11 +69,6 @@ class TestMultiBufferDataProvider : public media::MultiBuffer::DataProvider { return ret; } - void SetAvailableCallback(const base::Closure& cb) override { - DCHECK(!Available()); - cb_ = cb; - } - void SetDeferred(bool deferred) override { if (deferred) { if (max_blocks_after_defer_ > 0) { @@ -89,7 +89,7 @@ class TestMultiBufferDataProvider : public media::MultiBuffer::DataProvider { --blocks_until_deferred_; bool ret = true; - scoped_refptr<media::DataBuffer> block = new media::DataBuffer(kBlockSize); + scoped_refptr<DataBuffer> block = new DataBuffer(kBlockSize); size_t x = 0; size_t byte_pos = (fifo_.size() + pos_) * kBlockSize; for (x = 0; x < kBlockSize; x++, byte_pos++) { @@ -104,28 +104,27 @@ class TestMultiBufferDataProvider : public media::MultiBuffer::DataProvider { fifo_.push_back(DataBuffer::CreateEOSBuffer()); ret = false; } - cb_.Run(); + multibuffer_->OnDataProviderEvent(this); return ret; } private: - std::deque<scoped_refptr<media::DataBuffer>> fifo_; + std::deque<scoped_refptr<DataBuffer>> fifo_; MultiBufferBlockId pos_; int32_t blocks_until_deferred_; int32_t max_blocks_after_defer_; size_t file_size_; bool must_read_whole_file_; - base::Closure cb_; - media::TestRandom* rnd_; + MultiBuffer* multibuffer_; + TestRandom* rnd_; }; -class TestMultiBuffer : public media::MultiBuffer { +class TestMultiBuffer : public MultiBuffer { public: - explicit TestMultiBuffer( - int32_t shift, - const scoped_refptr<media::MultiBuffer::GlobalLRU>& lru, - media::TestRandom* rnd) - : media::MultiBuffer(shift, lru), + explicit TestMultiBuffer(int32_t shift, + const scoped_refptr<MultiBuffer::GlobalLRU>& lru, + TestRandom* rnd) + : MultiBuffer(shift, lru), range_supported_(false), create_ok_(true), max_writers_(10000), @@ -185,12 +184,14 @@ class TestMultiBuffer : public media::MultiBuffer { void SetRangeSupported(bool supported) { range_supported_ = supported; } protected: - DataProvider* CreateWriter(const MultiBufferBlockId& pos) override { + scoped_ptr<DataProvider> CreateWriter( + const MultiBufferBlockId& pos) override { DCHECK(create_ok_); writers_created_++; CHECK_LT(writers.size(), max_writers_); - return new TestMultiBufferDataProvider( - pos, file_size_, max_blocks_after_defer_, must_read_whole_file_, rnd_); + return scoped_ptr<DataProvider>(new FakeMultiBufferDataProvider( + pos, file_size_, max_blocks_after_defer_, must_read_whole_file_, this, + rnd_)); } void Prune(size_t max_to_free) override { // Prune should not cause additional writers to be spawned. @@ -209,35 +210,34 @@ class TestMultiBuffer : public media::MultiBuffer { int32_t max_blocks_after_defer_; bool must_read_whole_file_; int32_t writers_created_; - media::TestRandom* rnd_; + TestRandom* rnd_; }; -} class MultiBufferTest : public testing::Test { public: MultiBufferTest() : rnd_(42), - lru_(new media::MultiBuffer::GlobalLRU()), + lru_(new MultiBuffer::GlobalLRU()), multibuffer_(kBlockSizeShift, lru_, &rnd_) {} void Advance() { - CHECK(media::writers.size()); - media::writers[rnd_.Rand() % media::writers.size()]->Advance(); + CHECK(writers.size()); + writers[rnd_.Rand() % writers.size()]->Advance(); } bool AdvanceAll() { bool advanced = false; - for (size_t i = 0; i < media::writers.size(); i++) { - advanced |= media::writers[i]->Advance(); + for (size_t i = 0; i < writers.size(); i++) { + advanced |= writers[i]->Advance(); } multibuffer_.CheckLRUState(); return advanced; } protected: - media::TestRandom rnd_; - scoped_refptr<media::MultiBuffer::GlobalLRU> lru_; - media::TestMultiBuffer multibuffer_; + TestRandom rnd_; + scoped_refptr<MultiBuffer::GlobalLRU> lru_; + TestMultiBuffer multibuffer_; base::MessageLoop message_loop_; }; @@ -247,8 +247,8 @@ TEST_F(MultiBufferTest, ReadAll) { size_t end = 10000; multibuffer_.SetFileSize(10000); multibuffer_.SetMustReadWholeFile(true); - media::MultiBufferReader reader(&multibuffer_, pos, end, - base::Callback<void(int64_t, int64_t)>()); + MultiBufferReader reader(&multibuffer_, pos, end, + base::Callback<void(int64_t, int64_t)>()); reader.SetMaxBuffer(2000, 5000); reader.SetPreload(1000, 1000); while (pos < end) { @@ -275,8 +275,8 @@ TEST_F(MultiBufferTest, ReadAllAdvanceFirst) { size_t end = 10000; multibuffer_.SetFileSize(10000); multibuffer_.SetMustReadWholeFile(true); - media::MultiBufferReader reader(&multibuffer_, pos, end, - base::Callback<void(int64_t, int64_t)>()); + MultiBufferReader reader(&multibuffer_, pos, end, + base::Callback<void(int64_t, int64_t)>()); reader.SetMaxBuffer(2000, 5000); reader.SetPreload(1000, 1000); while (pos < end) { @@ -305,8 +305,8 @@ TEST_F(MultiBufferTest, ReadAllAdvanceFirst_NeverDefer) { multibuffer_.SetFileSize(10000); multibuffer_.SetMaxBlocksAfterDefer(-10000); multibuffer_.SetRangeSupported(true); - media::MultiBufferReader reader(&multibuffer_, pos, end, - base::Callback<void(int64_t, int64_t)>()); + MultiBufferReader reader(&multibuffer_, pos, end, + base::Callback<void(int64_t, int64_t)>()); reader.SetMaxBuffer(2000, 5000); reader.SetPreload(1000, 1000); while (pos < end) { @@ -336,8 +336,8 @@ TEST_F(MultiBufferTest, ReadAllAdvanceFirst_NeverDefer2) { multibuffer_.SetFileSize(10000); multibuffer_.SetMustReadWholeFile(true); multibuffer_.SetMaxBlocksAfterDefer(-10000); - media::MultiBufferReader reader(&multibuffer_, pos, end, - base::Callback<void(int64_t, int64_t)>()); + MultiBufferReader reader(&multibuffer_, pos, end, + base::Callback<void(int64_t, int64_t)>()); reader.SetMaxBuffer(2000, 5000); reader.SetPreload(1000, 1000); while (pos < end) { @@ -366,8 +366,8 @@ TEST_F(MultiBufferTest, LRUTest) { size_t pos = 0; size_t end = 10000; multibuffer_.SetFileSize(10000); - media::MultiBufferReader reader(&multibuffer_, pos, end, - base::Callback<void(int64_t, int64_t)>()); + MultiBufferReader reader(&multibuffer_, pos, end, + base::Callback<void(int64_t, int64_t)>()); reader.SetPreload(10000, 10000); // Note, no pinning, all data should end up in LRU. EXPECT_EQ(current_size, lru_->Size()); @@ -390,8 +390,8 @@ class ReadHelper { public: ReadHelper(size_t end, size_t max_read_size, - media::MultiBuffer* multibuffer, - media::TestRandom* rnd) + MultiBuffer* multibuffer, + TestRandom* rnd) : pos_(0), end_(end), max_read_size_(max_read_size), @@ -446,8 +446,8 @@ class ReadHelper { int64_t end_; int64_t max_read_size_; int64_t read_size_; - media::TestRandom* rnd_; - media::MultiBufferReader reader_; + TestRandom* rnd_; + MultiBufferReader reader_; }; TEST_F(MultiBufferTest, RandomTest) { @@ -462,7 +462,7 @@ TEST_F(MultiBufferTest, RandomTest) { for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { if (rnd_.Rand() & 1) { - if (!media::writers.empty()) + if (!writers.empty()) Advance(); } else { size_t j = rnd_.Rand() % read_helpers.size(); @@ -493,7 +493,7 @@ TEST_F(MultiBufferTest, RandomTest_RangeSupported) { for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { if (rnd_.Rand() & 1) { - if (!media::writers.empty()) + if (!writers.empty()) Advance(); } else { size_t j = rnd_.Rand() % read_helpers.size(); @@ -510,3 +510,5 @@ TEST_F(MultiBufferTest, RandomTest_RangeSupported) { read_helpers.pop_back(); } } + +} // namespace media diff --git a/media/blink/resource_multibuffer_data_provider.cc b/media/blink/resource_multibuffer_data_provider.cc new file mode 100644 index 0000000..78bd2a0 --- /dev/null +++ b/media/blink/resource_multibuffer_data_provider.cc @@ -0,0 +1,480 @@ +// Copyright 2015 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 "media/blink/resource_multibuffer_data_provider.h" + +#include "base/bind.h" +#include "base/bits.h" +#include "base/callback_helpers.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/strings/string_number_conversions.h" +#include "media/blink/active_loader.h" +#include "media/blink/cache_util.h" +#include "media/blink/media_blink_export.h" +#include "media/blink/url_index.h" +#include "net/http/http_byte_range.h" +#include "net/http/http_request_headers.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" + +using blink::WebFrame; +using blink::WebString; +using blink::WebURLError; +using blink::WebURLLoader; +using blink::WebURLLoaderOptions; +using blink::WebURLRequest; +using blink::WebURLResponse; + +namespace media { + +// The number of milliseconds to wait before retrying a failed load. +const int kLoaderFailedRetryDelayMs = 250; + +const int kHttpOK = 200; +const int kHttpPartialContent = 206; +const int kHttpRangeNotSatisfiable = 416; +const int kMaxRetries = 3; + +ResourceMultiBufferDataProvider::ResourceMultiBufferDataProvider( + UrlData* url_data, + MultiBufferBlockId pos) + : pos_(pos), + url_data_(url_data), + retries_(0), + cors_mode_(url_data->cors_mode()), + origin_(url_data->url().GetOrigin()), + weak_factory_(this) { + DCHECK(url_data_) << " pos = " << pos; + DCHECK_GE(pos, 0); +} + +void ResourceMultiBufferDataProvider::Start() { + // In the case of a re-start, throw away any half-finished blocks. + fifo_.clear(); + // Prepare the request. + WebURLRequest request(url_data_->url()); + // TODO(mkwst): Split this into video/audio. + request.setRequestContext(WebURLRequest::RequestContextVideo); + + request.setHTTPHeaderField( + WebString::fromUTF8(net::HttpRequestHeaders::kRange), + WebString::fromUTF8( + net::HttpByteRange::RightUnbounded(byte_pos()).GetHeaderValue())); + + url_data_->frame()->setReferrerForRequest(request, blink::WebURL()); + + // Disable compression, compression for audio/video doesn't make sense... + request.setHTTPHeaderField( + WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding), + WebString::fromUTF8("identity;q=1, *;q=0")); + + // Check for our test WebURLLoader. + scoped_ptr<WebURLLoader> loader; + if (test_loader_) { + loader = test_loader_.Pass(); + } else { + WebURLLoaderOptions options; + if (url_data_->cors_mode() == UrlData::CORS_UNSPECIFIED) { + options.allowCredentials = true; + options.crossOriginRequestPolicy = + WebURLLoaderOptions::CrossOriginRequestPolicyAllow; + } else { + options.exposeAllResponseHeaders = true; + // The author header set is empty, no preflight should go ahead. + options.preflightPolicy = WebURLLoaderOptions::PreventPreflight; + options.crossOriginRequestPolicy = + WebURLLoaderOptions::CrossOriginRequestPolicyUseAccessControl; + if (url_data_->cors_mode() == UrlData::CORS_USE_CREDENTIALS) + options.allowCredentials = true; + } + loader.reset(url_data_->frame()->createAssociatedURLLoader(options)); + } + + // Start the resource loading. + loader->loadAsynchronously(request, this); + active_loader_.reset(new ActiveLoader(loader.Pass())); +} + +ResourceMultiBufferDataProvider::~ResourceMultiBufferDataProvider() {} + +///////////////////////////////////////////////////////////////////////////// +// MultiBuffer::DataProvider implementation. +MultiBufferBlockId ResourceMultiBufferDataProvider::Tell() const { + return pos_; +} + +bool ResourceMultiBufferDataProvider::Available() const { + if (fifo_.empty()) + return false; + if (fifo_.back()->end_of_stream()) + return true; + if (fifo_.front()->data_size() == block_size()) + return true; + return false; +} + +scoped_refptr<DataBuffer> ResourceMultiBufferDataProvider::Read() { + DCHECK(Available()); + scoped_refptr<DataBuffer> ret = fifo_.front(); + fifo_.pop_front(); + ++pos_; + return ret; +} + +void ResourceMultiBufferDataProvider::SetDeferred(bool deferred) { + if (!active_loader_ || active_loader_->deferred() == deferred) + return; + active_loader_->SetDeferred(deferred); +} + +///////////////////////////////////////////////////////////////////////////// +// WebURLLoaderClient implementation. + +void ResourceMultiBufferDataProvider::willFollowRedirect( + WebURLLoader* loader, + WebURLRequest& newRequest, + const WebURLResponse& redirectResponse) { + redirects_to_ = newRequest.url(); + url_data_->set_valid_until(base::Time::Now() + + GetCacheValidUntil(redirectResponse)); + + // This test is vital for security! + if (cors_mode_ == UrlData::CORS_UNSPECIFIED) { + if (origin_ != redirects_to_.GetOrigin()) { + url_data_->Fail(); + } + } +} + +void ResourceMultiBufferDataProvider::didSendData( + WebURLLoader* loader, + unsigned long long bytes_sent, + unsigned long long total_bytes_to_be_sent) { + NOTIMPLEMENTED(); +} + +void ResourceMultiBufferDataProvider::didReceiveResponse( + WebURLLoader* loader, + const WebURLResponse& response) { +#if ENABLE_DLOG + string version; + switch (response.httpVersion()) { + case WebURLResponse::HTTPVersion_0_9: + version = "0.9"; + break; + case WebURLResponse::HTTPVersion_1_0: + version = "1.0"; + break; + case WebURLResponse::HTTPVersion_1_1: + version = "1.1"; + break; + case WebURLResponse::HTTPVersion_2_0: + version = "2.1"; + break; + } + DVLOG(1) << "didReceiveResponse: HTTP/" << version << " " + << response.httpStatusCode(); +#endif + DCHECK(active_loader_); + + scoped_refptr<UrlData> destination_url_data(url_data_); + + UrlIndex* url_index = url_data_->url_index(); + + if (!redirects_to_.is_empty()) { + if (!url_index) { + // We've been disconnected from the url index. + // That means the url_index_ has been destroyed, which means we do not + // need to do anything clever. + return; + } + destination_url_data = url_index->GetByUrl(redirects_to_, cors_mode_); + redirects_to_ = GURL(); + } + + base::Time last_modified; + if (base::Time::FromString( + response.httpHeaderField("Last-Modified").utf8().data(), + &last_modified)) { + destination_url_data->set_last_modified(last_modified); + } + + destination_url_data->set_valid_until(base::Time::Now() + + GetCacheValidUntil(response)); + + uint32 reasons = GetReasonsForUncacheability(response); + destination_url_data->set_cacheable(reasons == 0); + UMA_HISTOGRAM_BOOLEAN("Media.CacheUseful", reasons == 0); + int shift = 0; + int max_enum = base::bits::Log2Ceiling(kMaxReason); + while (reasons) { + DCHECK_LT(shift, max_enum); // Sanity check. + if (reasons & 0x1) { + UMA_HISTOGRAM_ENUMERATION("Media.UncacheableReason", shift, + max_enum); // PRESUBMIT_IGNORE_UMA_MAX + } + + reasons >>= 1; + ++shift; + } + + // Expected content length can be |kPositionNotSpecified|, in that case + // |content_length_| is not specified and this is a streaming response. + int64 content_length = response.expectedContentLength(); + + // We make a strong assumption that when we reach here we have either + // received a response from HTTP/HTTPS protocol or the request was + // successful (in particular range request). So we only verify the partial + // response for HTTP and HTTPS protocol. + if (destination_url_data->url().SchemeIsHTTPOrHTTPS()) { + bool partial_response = (response.httpStatusCode() == kHttpPartialContent); + bool ok_response = (response.httpStatusCode() == kHttpOK); + + // Check to see whether the server supports byte ranges. + std::string accept_ranges = + response.httpHeaderField("Accept-Ranges").utf8(); + if (accept_ranges.find("bytes") != std::string::npos) + destination_url_data->set_range_supported(); + + // If we have verified the partial response and it is correct. + // It's also possible for a server to support range requests + // without advertising "Accept-Ranges: bytes". + if (partial_response && VerifyPartialResponse(response)) { + destination_url_data->set_range_supported(); + } else if (ok_response && pos_ == 0) { + // We accept a 200 response for a Range:0- request, trusting the + // Accept-Ranges header, because Apache thinks that's a reasonable thing + // to return. + destination_url_data->set_length(content_length); + } else if (response.httpStatusCode() == kHttpRangeNotSatisfiable) { + // Really, we should never request a range that doesn't exist, but + // if we do, let's handle it in a sane way. + // Unsatisfiable range + fifo_.push_back(DataBuffer::CreateEOSBuffer()); + destination_url_data->multibuffer()->OnDataProviderEvent(this); + return; + } else { + destination_url_data->Fail(); + return; + } + } else { + destination_url_data->set_range_supported(); + if (content_length != kPositionNotSpecified) { + destination_url_data->set_length(content_length + byte_pos()); + } + } + + if (url_index) { + destination_url_data = url_index->TryInsert(destination_url_data); + } + + if (destination_url_data != url_data_) { + // At this point, we've encountered a redirect, or found a better url data + // instance for the data that we're about to download. + + // First, let's take a ref on the current url data. + scoped_refptr<UrlData> old_url_data(url_data_); + destination_url_data->Use(); + + // Take ownership of ourselves. (From the multibuffer) + scoped_ptr<DataProvider> self( + url_data_->multibuffer()->RemoveProvider(this)); + url_data_ = destination_url_data.get(); + // Give the ownership to our new owner. + url_data_->multibuffer()->AddProvider(self.Pass()); + + // Call callback to let upstream users know about the transfer. + // This will merge the data from the two multibuffers and + // cause clients to start using the new UrlData. + old_url_data->RedirectTo(destination_url_data); + } +} + +void ResourceMultiBufferDataProvider::didReceiveData(WebURLLoader* loader, + const char* data, + int data_length, + int encoded_data_length) { + DVLOG(1) << "didReceiveData: " << data_length << " bytes"; + DCHECK(!Available()); + DCHECK(active_loader_); + DCHECK_GT(data_length, 0); + + // When we receive data, we allow more retries. + retries_ = 0; + + while (data_length) { + if (fifo_.empty() || fifo_.back()->data_size() == block_size()) { + fifo_.push_back(new DataBuffer(block_size())); + fifo_.back()->set_data_size(0); + } + int last_block_size = fifo_.back()->data_size(); + int to_append = std::min<int>(data_length, block_size() - last_block_size); + DCHECK_GT(to_append, 0); + memcpy(fifo_.back()->writable_data() + last_block_size, data, to_append); + data += to_append; + fifo_.back()->set_data_size(last_block_size + to_append); + data_length -= to_append; + } + + if (Available()) + url_data_->multibuffer()->OnDataProviderEvent(this); + + // Beware, this object might be deleted here. +} + +void ResourceMultiBufferDataProvider::didDownloadData(WebURLLoader* loader, + int dataLength, + int encoded_data_length) { + NOTIMPLEMENTED(); +} + +void ResourceMultiBufferDataProvider::didReceiveCachedMetadata( + WebURLLoader* loader, + const char* data, + int data_length) { + NOTIMPLEMENTED(); +} + +void ResourceMultiBufferDataProvider::didFinishLoading( + WebURLLoader* loader, + double finishTime, + int64_t total_encoded_data_length) { + DVLOG(1) << "didFinishLoading"; + DCHECK(active_loader_.get()); + DCHECK(!Available()); + + // We're done with the loader. + active_loader_.reset(); + + // If we didn't know the |instance_size_| we do now. + int64_t size = byte_pos(); + if (!fifo_.empty()) + size += fifo_.back()->data_size(); + + // This request reports something smaller than what we've seen in the past, + // Maybe it's transient error? + if (url_data_->length() != kPositionNotSpecified && + size < url_data_->length()) { + if (retries_ < kMaxRetries) { + fifo_.clear(); + retries_++; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start, + weak_factory_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs)); + return; + } else { + scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass(); + url_data_->Fail(); + return; + } + } + + url_data_->set_length(size); + fifo_.push_back(DataBuffer::CreateEOSBuffer()); + + DCHECK(Available()); + url_data_->multibuffer()->OnDataProviderEvent(this); + + // Beware, this object might be deleted here. +} + +void ResourceMultiBufferDataProvider::didFail(WebURLLoader* loader, + const WebURLError& error) { + DVLOG(1) << "didFail: reason=" << error.reason + << ", isCancellation=" << error.isCancellation + << ", domain=" << error.domain.utf8().data() + << ", localizedDescription=" + << error.localizedDescription.utf8().data(); + DCHECK(active_loader_.get()); + + if (retries_ < kMaxRetries) { + retries_++; + base::MessageLoop::current()->PostDelayedTask( + FROM_HERE, base::Bind(&ResourceMultiBufferDataProvider::Start, + weak_factory_.GetWeakPtr()), + base::TimeDelta::FromMilliseconds(kLoaderFailedRetryDelayMs)); + } else { + // We don't need to continue loading after failure. + // + // Keep it alive until we exit this method so that |error| remains valid. + scoped_ptr<ActiveLoader> active_loader = active_loader_.Pass(); + url_data_->Fail(); + } +} + +bool ResourceMultiBufferDataProvider::ParseContentRange( + const std::string& content_range_str, + int64* first_byte_position, + int64* last_byte_position, + int64* instance_size) { + const std::string kUpThroughBytesUnit = "bytes "; + if (content_range_str.find(kUpThroughBytesUnit) != 0) + return false; + std::string range_spec = + content_range_str.substr(kUpThroughBytesUnit.length()); + size_t dash_offset = range_spec.find("-"); + size_t slash_offset = range_spec.find("/"); + + if (dash_offset == std::string::npos || slash_offset == std::string::npos || + slash_offset < dash_offset || slash_offset + 1 == range_spec.length()) { + return false; + } + if (!base::StringToInt64(range_spec.substr(0, dash_offset), + first_byte_position) || + !base::StringToInt64( + range_spec.substr(dash_offset + 1, slash_offset - dash_offset - 1), + last_byte_position)) { + return false; + } + if (slash_offset == range_spec.length() - 2 && + range_spec[slash_offset + 1] == '*') { + *instance_size = kPositionNotSpecified; + } else { + if (!base::StringToInt64(range_spec.substr(slash_offset + 1), + instance_size)) { + return false; + } + } + if (*last_byte_position < *first_byte_position || + (*instance_size != kPositionNotSpecified && + *last_byte_position >= *instance_size)) { + return false; + } + + return true; +} + +int64_t ResourceMultiBufferDataProvider::byte_pos() const { + int64_t ret = pos_; + return ret << url_data_->multibuffer()->block_size_shift(); +} + +int64_t ResourceMultiBufferDataProvider::block_size() const { + int64_t ret = 1; + return ret << url_data_->multibuffer()->block_size_shift(); +} + +bool ResourceMultiBufferDataProvider::VerifyPartialResponse( + const WebURLResponse& response) { + int64 first_byte_position, last_byte_position, instance_size; + if (!ParseContentRange(response.httpHeaderField("Content-Range").utf8(), + &first_byte_position, &last_byte_position, + &instance_size)) { + return false; + } + + if (url_data_->length() == kPositionNotSpecified) { + url_data_->set_length(instance_size); + } + + if (byte_pos() != first_byte_position) { + return false; + } + + return true; +} + +} // namespace media diff --git a/media/blink/resource_multibuffer_data_provider.h b/media/blink/resource_multibuffer_data_provider.h new file mode 100644 index 0000000..89b70e5 --- /dev/null +++ b/media/blink/resource_multibuffer_data_provider.h @@ -0,0 +1,126 @@ +// Copyright 2015 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 MEDIA_BLINK_RESOURCE_MULTIBUFFER_DATA_PROVIDER_H_ +#define MEDIA_BLINK_RESOURCE_MULTIBUFFER_DATA_PROVIDER_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "media/blink/active_loader.h" +#include "media/blink/media_blink_export.h" +#include "media/blink/multibuffer.h" +#include "media/blink/url_index.h" +#include "third_party/WebKit/public/platform/WebURLLoader.h" +#include "third_party/WebKit/public/platform/WebURLLoaderClient.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "url/gurl.h" + +namespace media { + +class MEDIA_BLINK_EXPORT ResourceMultiBufferDataProvider + : NON_EXPORTED_BASE(public MultiBuffer::DataProvider), + NON_EXPORTED_BASE(public blink::WebURLLoaderClient) { + public: + ResourceMultiBufferDataProvider(UrlData* url_data, MultiBufferBlockId pos); + ~ResourceMultiBufferDataProvider() override; + + // Virtual for testing purposes. + virtual void Start(); + + // MultiBuffer::DataProvider implementation + MultiBufferBlockId Tell() const override; + bool Available() const override; + scoped_refptr<DataBuffer> Read() override; + void SetDeferred(bool defer) override; + + // blink::WebURLLoaderClient implementation. + void willFollowRedirect( + blink::WebURLLoader* loader, + blink::WebURLRequest& newRequest, + const blink::WebURLResponse& redirectResponse) override; + void didSendData(blink::WebURLLoader* loader, + unsigned long long bytesSent, + unsigned long long totalBytesToBeSent) override; + void didReceiveResponse(blink::WebURLLoader* loader, + const blink::WebURLResponse& response) override; + void didDownloadData(blink::WebURLLoader* loader, + int data_length, + int encoded_data_length) override; + void didReceiveData(blink::WebURLLoader* loader, + const char* data, + int data_length, + int encoded_data_length) override; + void didReceiveCachedMetadata(blink::WebURLLoader* loader, + const char* data, + int dataLength) override; + void didFinishLoading(blink::WebURLLoader* loader, + double finishTime, + int64_t total_encoded_data_length) override; + void didFail(blink::WebURLLoader* loader, const blink::WebURLError&) override; + + // Use protected instead of private for testing purposes. + protected: + friend class MultibufferDataSourceTest; + friend class ResourceMultiBufferDataProviderTest; + friend class MockBufferedDataSource; + + // Parse a Content-Range header into its component pieces and return true if + // each of the expected elements was found & parsed correctly. + // |*instance_size| may be set to kPositionNotSpecified if the range ends in + // "/*". + // NOTE: only public for testing! This is an implementation detail of + // VerifyPartialResponse (a private method). + static bool ParseContentRange(const std::string& content_range_str, + int64* first_byte_position, + int64* last_byte_position, + int64* instance_size); + + int64_t byte_pos() const; + int64_t block_size() const; + + // If we have made a range request, verify the response from the server. + bool VerifyPartialResponse(const blink::WebURLResponse& response); + + // Current Position. + MultiBufferBlockId pos_; + + // This is where we actually get read data from. + // We don't need (or want) a scoped_refptr for this one, because + // we are owned by it. Note that we may change this when we encounter + // a redirect because we actually change ownership. + UrlData* url_data_; + + // Temporary storage for incoming data. + std::list<scoped_refptr<DataBuffer>> fifo_; + + // How many retries have we done at the current position. + int retries_; + + // Copy of url_data_->cors_mode() + // const to make it obvious that redirects cannot change it. + const UrlData::CORSMode cors_mode_; + + // The origin for the initial request. + // const to make it obvious that redirects cannot change it. + const GURL origin_; + + // Keeps track of an active WebURLLoader and associated state. + scoped_ptr<ActiveLoader> active_loader_; + + // Injected WebURLLoader instance for testing purposes. + scoped_ptr<blink::WebURLLoader> test_loader_; + + // When we encounter a redirect, this is the source of the redirect. + GURL redirects_to_; + + base::WeakPtrFactory<ResourceMultiBufferDataProvider> weak_factory_; +}; + +} // namespace media + +#endif // MEDIA_BLINK_RESOURCE_MULTIBUFFER_RESOURCE_LOADER_H_ diff --git a/media/blink/resource_multibuffer_data_provider_unittest.cc b/media/blink/resource_multibuffer_data_provider_unittest.cc new file mode 100644 index 0000000..a60edeb --- /dev/null +++ b/media/blink/resource_multibuffer_data_provider_unittest.cc @@ -0,0 +1,330 @@ +// Copyright 2015 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 <algorithm> +#include <string> + +#include "base/bind.h" +#include "base/format_macros.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "media/base/media_log.h" +#include "media/base/seekable_buffer.h" +#include "media/blink/mock_webframeclient.h" +#include "media/blink/mock_weburlloader.h" +#include "media/blink/resource_multibuffer_data_provider.h" +#include "media/blink/url_index.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_util.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebURLError.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/platform/WebURLResponse.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebView.h" + +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; +using ::testing::Truly; +using ::testing::NiceMock; + +using blink::WebLocalFrame; +using blink::WebString; +using blink::WebURLError; +using blink::WebURLResponse; +using blink::WebView; + +namespace media { + +const char kHttpUrl[] = "http://test"; +const char kHttpRedirect[] = "http://test/ing"; + +const int kDataSize = 1024; +const int kHttpOK = 200; +const int kHttpPartialContent = 206; + +enum NetworkState { NONE, LOADED, LOADING }; + +// Predicate that tests that request disallows compressed data. +static bool CorrectAcceptEncoding(const blink::WebURLRequest& request) { + std::string value = + request.httpHeaderField( + WebString::fromUTF8(net::HttpRequestHeaders::kAcceptEncoding)) + .utf8(); + return (value.find("identity;q=1") != std::string::npos) && + (value.find("*;q=0") != std::string::npos); +} + +class ResourceMultiBufferDataProviderTest : public testing::Test { + public: + ResourceMultiBufferDataProviderTest() + : view_(WebView::create(nullptr)), + frame_(WebLocalFrame::create(blink::WebTreeScopeType::Document, + &client_)) { + view_->setMainFrame(frame_); + url_index_.reset(new UrlIndex(frame_, 0)); + + for (int i = 0; i < kDataSize; ++i) { + data_[i] = i; + } + } + + virtual ~ResourceMultiBufferDataProviderTest() { + view_->close(); + frame_->close(); + } + + void Initialize(const char* url, int first_position) { + gurl_ = GURL(url); + url_data_ = url_index_->GetByUrl(gurl_, UrlData::CORS_UNSPECIFIED); + DCHECK(url_data_); + DCHECK(url_data_->frame()); + url_data_->OnRedirect( + base::Bind(&ResourceMultiBufferDataProviderTest::RedirectCallback, + base::Unretained(this))); + + first_position_ = first_position; + + scoped_ptr<ResourceMultiBufferDataProvider> loader( + new ResourceMultiBufferDataProvider(url_data_.get(), first_position_)); + loader_ = loader.get(); + url_data_->multibuffer()->AddProvider(loader.Pass()); + + // |test_loader_| will be used when Start() is called. + url_loader_ = new NiceMock<MockWebURLLoader>(); + loader_->test_loader_ = scoped_ptr<blink::WebURLLoader>(url_loader_); + } + + void Start() { + InSequence s; + EXPECT_CALL(*url_loader_, + loadAsynchronously(Truly(CorrectAcceptEncoding), loader_)); + + loader_->Start(); + } + + void FullResponse(int64 instance_size, bool ok = true) { + WebURLResponse response(gurl_); + response.setHTTPHeaderField( + WebString::fromUTF8("Content-Length"), + WebString::fromUTF8(base::StringPrintf("%" PRId64, instance_size))); + response.setExpectedContentLength(instance_size); + response.setHTTPStatusCode(kHttpOK); + loader_->didReceiveResponse(url_loader_, response); + + if (ok) { + EXPECT_EQ(instance_size, url_data_->length()); + } + + EXPECT_FALSE(url_data_->range_supported()); + } + + void PartialResponse(int64 first_position, + int64 last_position, + int64 instance_size) { + PartialResponse(first_position, last_position, instance_size, false, true); + } + + void PartialResponse(int64 first_position, + int64 last_position, + int64 instance_size, + bool chunked, + bool accept_ranges) { + WebURLResponse response(gurl_); + response.setHTTPHeaderField( + WebString::fromUTF8("Content-Range"), + WebString::fromUTF8( + base::StringPrintf("bytes " + "%" PRId64 "-%" PRId64 "/%" PRId64, + first_position, last_position, instance_size))); + + // HTTP 1.1 doesn't permit Content-Length with Transfer-Encoding: chunked. + int64 content_length = -1; + if (chunked) { + response.setHTTPHeaderField(WebString::fromUTF8("Transfer-Encoding"), + WebString::fromUTF8("chunked")); + } else { + content_length = last_position - first_position + 1; + } + response.setExpectedContentLength(content_length); + + // A server isn't required to return Accept-Ranges even though it might. + if (accept_ranges) { + response.setHTTPHeaderField(WebString::fromUTF8("Accept-Ranges"), + WebString::fromUTF8("bytes")); + } + + response.setHTTPStatusCode(kHttpPartialContent); + loader_->didReceiveResponse(url_loader_, response); + + EXPECT_EQ(instance_size, url_data_->length()); + + // A valid partial response should always result in this being true. + EXPECT_TRUE(url_data_->range_supported()); + } + + void Redirect(const char* url) { + GURL redirectUrl(url); + blink::WebURLRequest newRequest(redirectUrl); + blink::WebURLResponse redirectResponse(gurl_); + + EXPECT_CALL(*this, RedirectCallback(_)) + .WillOnce( + Invoke(this, &ResourceMultiBufferDataProviderTest::SetUrlData)); + + loader_->willFollowRedirect(url_loader_, newRequest, redirectResponse); + + base::MessageLoop::current()->RunUntilIdle(); + } + + void StopWhenLoad() { + InSequence s; + EXPECT_CALL(*url_loader_, cancel()); + loader_ = nullptr; + url_data_ = nullptr; + } + + // Helper method to write to |loader_| from |data_|. + void WriteLoader(int position, int size) { + loader_->didReceiveData( + url_loader_, reinterpret_cast<char*>(data_ + position), size, size); + } + + void WriteData(int size) { + scoped_ptr<char[]> data(new char[size]); + loader_->didReceiveData(url_loader_, data.get(), size, size); + } + + // Verifies that data in buffer[0...size] is equal to data_[pos...pos+size]. + void VerifyBuffer(uint8* buffer, int pos, int size) { + EXPECT_EQ(0, memcmp(buffer, data_ + pos, size)); + } + + bool HasActiveLoader() { return loader_->active_loader_; } + MOCK_METHOD1(RedirectCallback, void(const scoped_refptr<UrlData>&)); + + void SetUrlData(const scoped_refptr<UrlData>& new_url_data) { + url_data_ = new_url_data; + } + + protected: + GURL gurl_; + int64 first_position_; + + scoped_ptr<UrlIndex> url_index_; + scoped_refptr<UrlData> url_data_; + scoped_refptr<UrlData> redirected_to_; + // The loader is owned by the UrlData above. + ResourceMultiBufferDataProvider* loader_; + NiceMock<MockWebURLLoader>* url_loader_; + + MockWebFrameClient client_; + WebView* view_; + WebLocalFrame* frame_; + + base::MessageLoop message_loop_; + + uint8 data_[kDataSize]; + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceMultiBufferDataProviderTest); +}; + +TEST_F(ResourceMultiBufferDataProviderTest, StartStop) { + Initialize(kHttpUrl, 0); + Start(); + StopWhenLoad(); +} + +// Tests that a bad HTTP response is recived, e.g. file not found. +TEST_F(ResourceMultiBufferDataProviderTest, BadHttpResponse) { + Initialize(kHttpUrl, 0); + Start(); + + EXPECT_CALL(*this, RedirectCallback(scoped_refptr<UrlData>(nullptr))); + + WebURLResponse response(gurl_); + response.setHTTPStatusCode(404); + response.setHTTPStatusText("Not Found\n"); + loader_->didReceiveResponse(url_loader_, response); + StopWhenLoad(); +} + +// Tests that partial content is requested but not fulfilled. +TEST_F(ResourceMultiBufferDataProviderTest, NotPartialResponse) { + Initialize(kHttpUrl, 100); + Start(); + FullResponse(1024, false); + StopWhenLoad(); +} + +// Tests that a 200 response is received. +TEST_F(ResourceMultiBufferDataProviderTest, FullResponse) { + Initialize(kHttpUrl, 0); + Start(); + FullResponse(1024); + StopWhenLoad(); +} + +// Tests that a partial content response is received. +TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse) { + Initialize(kHttpUrl, 100); + Start(); + PartialResponse(100, 200, 1024); + StopWhenLoad(); +} + +TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse_Chunked) { + Initialize(kHttpUrl, 100); + Start(); + PartialResponse(100, 200, 1024, true, true); + StopWhenLoad(); +} + +TEST_F(ResourceMultiBufferDataProviderTest, PartialResponse_NoAcceptRanges) { + Initialize(kHttpUrl, 100); + Start(); + PartialResponse(100, 200, 1024, false, false); + StopWhenLoad(); +} + +TEST_F(ResourceMultiBufferDataProviderTest, + PartialResponse_ChunkedNoAcceptRanges) { + Initialize(kHttpUrl, 100); + Start(); + PartialResponse(100, 200, 1024, true, false); + StopWhenLoad(); +} + +// Tests that an invalid partial response is received. +TEST_F(ResourceMultiBufferDataProviderTest, InvalidPartialResponse) { + Initialize(kHttpUrl, 0); + Start(); + + EXPECT_CALL(*this, RedirectCallback(scoped_refptr<UrlData>(nullptr))); + + WebURLResponse response(gurl_); + response.setHTTPHeaderField( + WebString::fromUTF8("Content-Range"), + WebString::fromUTF8(base::StringPrintf("bytes " + "%d-%d/%d", + 1, 10, 1024))); + response.setExpectedContentLength(10); + response.setHTTPStatusCode(kHttpPartialContent); + loader_->didReceiveResponse(url_loader_, response); + StopWhenLoad(); +} + +TEST_F(ResourceMultiBufferDataProviderTest, TestRedirects) { + // Test redirect. + Initialize(kHttpUrl, 0); + Start(); + Redirect(kHttpRedirect); + FullResponse(1024); + StopWhenLoad(); +} + +} // namespace media diff --git a/media/blink/url_index.cc b/media/blink/url_index.cc new file mode 100644 index 0000000..125c445 --- /dev/null +++ b/media/blink/url_index.cc @@ -0,0 +1,237 @@ +// Copyright 2015 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 <set> +#include <utility> + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/time/time.h" +#include "media/blink/resource_multibuffer_data_provider.h" +#include "media/blink/url_index.h" + +namespace media { + +const int kBlockSizeShift = 15; // 1<<15 == 32kb +const int kUrlMappingTimeoutSeconds = 300; + +ResourceMultiBuffer::ResourceMultiBuffer(UrlData* url_data, int block_shift) + : MultiBuffer(block_shift, url_data->url_index_->lru_), + url_data_(url_data) {} + +ResourceMultiBuffer::~ResourceMultiBuffer() {} + +scoped_ptr<MultiBuffer::DataProvider> ResourceMultiBuffer::CreateWriter( + const MultiBufferBlockId& pos) { + ResourceMultiBufferDataProvider* ret = + new ResourceMultiBufferDataProvider(url_data_, pos); + ret->Start(); + return scoped_ptr<MultiBuffer::DataProvider>(ret); +} + +bool ResourceMultiBuffer::RangeSupported() const { + return url_data_->range_supported_; +} + +void ResourceMultiBuffer::OnEmpty() { + url_data_->OnEmpty(); +} + +UrlData::UrlData(const GURL& url, + CORSMode cors_mode, + const base::WeakPtr<UrlIndex>& url_index) + : url_(url), + cors_mode_(cors_mode), + url_index_(url_index), + length_(kPositionNotSpecified), + range_supported_(false), + cacheable_(false), + last_used_(), + multibuffer_(this, url_index_->block_shift_), + frame_(url_index->frame()) {} + +UrlData::~UrlData() {} + +std::pair<GURL, UrlData::CORSMode> UrlData::key() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return std::make_pair(url(), cors_mode()); +} + +void UrlData::set_valid_until(base::Time valid_until) { + DCHECK(thread_checker_.CalledOnValidThread()); + valid_until_ = valid_until; +} + +void UrlData::MergeFrom(const scoped_refptr<UrlData>& other) { + // We're merging from another UrlData that refers to the *same* + // resource, so when we merge the metadata, we can use the most + // optimistic values. + DCHECK(thread_checker_.CalledOnValidThread()); + valid_until_ = std::max(valid_until_, other->valid_until_); + // set_length() will not override the length if already known. + set_length(other->length_); + cacheable_ |= other->cacheable_; + range_supported_ |= other->range_supported_; + if (last_modified_.is_null()) { + last_modified_ = other->last_modified_; + } + multibuffer()->MergeFrom(other->multibuffer()); +} + +void UrlData::set_cacheable(bool cacheable) { + DCHECK(thread_checker_.CalledOnValidThread()); + cacheable_ = cacheable; +} + +void UrlData::set_length(int64 length) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (length != kPositionNotSpecified) { + length_ = length; + } +} + +void UrlData::RedirectTo(const scoped_refptr<UrlData>& url_data) { + DCHECK(thread_checker_.CalledOnValidThread()); + // Copy any cached data over to the new location. + url_data->multibuffer()->MergeFrom(multibuffer()); + + std::vector<RedirectCB> redirect_callbacks; + redirect_callbacks.swap(redirect_callbacks_); + for (const RedirectCB& cb : redirect_callbacks) { + cb.Run(url_data); + } +} + +void UrlData::Fail() { + DCHECK(thread_checker_.CalledOnValidThread()); + // Handled similar to a redirect. + std::vector<RedirectCB> redirect_callbacks; + redirect_callbacks.swap(redirect_callbacks_); + for (const RedirectCB& cb : redirect_callbacks) { + cb.Run(nullptr); + } +} + +void UrlData::OnRedirect(const RedirectCB& cb) { + DCHECK(thread_checker_.CalledOnValidThread()); + redirect_callbacks_.push_back(cb); +} + +void UrlData::Use() { + DCHECK(thread_checker_.CalledOnValidThread()); + last_used_ = base::Time::Now(); +} + +void UrlData::OnEmpty() { + DCHECK(thread_checker_.CalledOnValidThread()); + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(&UrlIndex::RemoveUrlDataIfEmpty, url_index_, + scoped_refptr<UrlData>(this))); +} + +bool UrlData::Valid() const { + DCHECK(thread_checker_.CalledOnValidThread()); + base::Time now = base::Time::Now(); + if (!range_supported_) + return false; + // When ranges are not supported, we cannot re-use cached data. + if (valid_until_ > now) + return true; + if (now - last_used_ < + base::TimeDelta::FromSeconds(kUrlMappingTimeoutSeconds)) + return true; + return false; +} + +void UrlData::set_last_modified(base::Time last_modified) { + DCHECK(thread_checker_.CalledOnValidThread()); + last_modified_ = last_modified; +} + +void UrlData::set_range_supported() { + DCHECK(thread_checker_.CalledOnValidThread()); + range_supported_ = true; +} + +ResourceMultiBuffer* UrlData::multibuffer() { + DCHECK(thread_checker_.CalledOnValidThread()); + return &multibuffer_; +} + +size_t UrlData::CachedSize() { + DCHECK(thread_checker_.CalledOnValidThread()); + return multibuffer()->map().size(); +} + +UrlIndex::UrlIndex(blink::WebFrame* frame) : UrlIndex(frame, kBlockSizeShift) {} + +UrlIndex::UrlIndex(blink::WebFrame* frame, int block_shift) + : frame_(frame), + lru_(new MultiBuffer::GlobalLRU()), + block_shift_(block_shift), + weak_factory_(this) {} + +UrlIndex::~UrlIndex() {} + +void UrlIndex::RemoveUrlDataIfEmpty(const scoped_refptr<UrlData>& url_data) { + if (!url_data->multibuffer()->map().empty()) + return; + + auto i = by_url_.find(url_data->key()); + if (i != by_url_.end() && i->second == url_data) + by_url_.erase(i); +} + +scoped_refptr<UrlData> UrlIndex::GetByUrl(const GURL& gurl, + UrlData::CORSMode cors_mode) { + auto i = by_url_.find(std::make_pair(gurl, cors_mode)); + if (i != by_url_.end() && i->second->Valid()) { + return i->second; + } + return NewUrlData(gurl, cors_mode); +} + +scoped_refptr<UrlData> UrlIndex::NewUrlData(const GURL& url, + UrlData::CORSMode cors_mode) { + return new UrlData(url, cors_mode, weak_factory_.GetWeakPtr()); +} + +scoped_refptr<UrlData> UrlIndex::TryInsert( + const scoped_refptr<UrlData>& url_data) { + scoped_refptr<UrlData>* by_url_slot; + bool urldata_valid = url_data->Valid(); + if (urldata_valid) { + by_url_slot = &by_url_.insert(std::make_pair(url_data->key(), url_data)) + .first->second; + } else { + std::map<UrlData::KeyType, scoped_refptr<UrlData>>::iterator iter; + iter = by_url_.find(url_data->key()); + if (iter == by_url_.end()) + return url_data; + by_url_slot = &iter->second; + } + if (*by_url_slot == url_data) + return url_data; + + // TODO(hubbe): Support etag validation. + if (!url_data->last_modified().is_null()) { + if ((*by_url_slot)->last_modified() != url_data->last_modified()) { + if (urldata_valid) + *by_url_slot = url_data; + return url_data; + } + } + // Check if we should replace the in-cache url data with our url data. + if (urldata_valid) { + if ((!(*by_url_slot)->Valid() || + url_data->CachedSize() > (*by_url_slot)->CachedSize())) { + *by_url_slot = url_data; + } else { + (*by_url_slot)->MergeFrom(url_data); + } + } + return *by_url_slot; +} + +} // namespace media diff --git a/media/blink/url_index.h b/media/blink/url_index.h new file mode 100644 index 0000000..56b73c2 --- /dev/null +++ b/media/blink/url_index.h @@ -0,0 +1,241 @@ +// Copyright 2015 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 MEDIA_BLINK_URL_INDEX_H_ +#define MEDIA_BLINK_URL_INDEX_H_ + +#include <map> +#include <vector> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "media/blink/lru.h" +#include "media/blink/media_blink_export.h" +#include "media/blink/multibuffer.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "url/gurl.h" + +namespace media { + +const int64 kPositionNotSpecified = -1; + +class UrlData; + +// A multibuffer for loading media resources which knows +// how to create MultiBufferDataProviders to load data +// into the cache. +class MEDIA_BLINK_EXPORT ResourceMultiBuffer + : NON_EXPORTED_BASE(public MultiBuffer) { + public: + ResourceMultiBuffer(UrlData* url_data_, int block_shift); + ~ResourceMultiBuffer() override; + + // MultiBuffer implementation. + scoped_ptr<MultiBuffer::DataProvider> CreateWriter( + const BlockId& pos) override; + bool RangeSupported() const override; + void OnEmpty() override; + + protected: + // Do not access from destructor, it is a pointer to the + // object that contains us. + UrlData* url_data_; +}; + +class UrlIndex; + +// All the data & metadata for a single resource. +// Data is cached using a MultiBuffer instance. +class MEDIA_BLINK_EXPORT UrlData : public base::RefCounted<UrlData> { + public: + // Keep in sync with WebMediaPlayer::CORSMode. + enum CORSMode { CORS_UNSPECIFIED, CORS_ANONYMOUS, CORS_USE_CREDENTIALS }; + typedef std::pair<GURL, CORSMode> KeyType; + + // Accessors + const GURL& url() const { return url_; } + + // Cross-origin access mode + CORSMode cors_mode() const { return cors_mode_; } + + // Are HTTP range requests supported? + bool range_supported() const { return range_supported_; } + + // True if we found a reason why this URL won't be stored in the + // HTTP disk cache. + bool cacheable() const { return cacheable_; } + + // Last used time. + base::Time last_used() const { return last_used_; } + + // Last modified time. + base::Time last_modified() const { return last_modified_; } + + // Expiration time. + base::Time valid_until() const { return valid_until_; } + + // The key used by UrlIndex to find this UrlData. + KeyType key() const; + + // Length of data associated with url or |kPositionNotSpecified| + int64 length() const { return length_; } + + // Returns the number of blocks cached for this resource. + size_t CachedSize(); + + // Returns our url_index, or nullptr if it's been destroyed. + UrlIndex* url_index() const { return url_index_.get(); } + + // Notifies the url index that this is currently used. + // The url <-> URLData mapping will be eventually be invalidated if + // this is not called regularly. + void Use(); + + // Setters. + void set_length(int64 length); + void set_cacheable(bool cacheable); + void set_valid_until(base::Time valid_until); + void set_range_supported(); + void set_last_modified(base::Time last_modified); + + // A redirect has occured (or we've found a better UrlData for the same + // resource). + void RedirectTo(const scoped_refptr<UrlData>& to); + + // Fail, tell all clients that a failure has occured. + void Fail(); + + // Callback for receving notifications when a redirect occurs. + typedef base::Callback<void(const scoped_refptr<UrlData>&)> RedirectCB; + + // Register a callback to be called when a redirect occurs. + // Callbacks are cleared when a redirect occurs, so clients must call + // OnRedirect again if they wish to continue receiving callbacks. + void OnRedirect(const RedirectCB& cb); + + // Returns true it is valid to keep using this to access cached data. + // A single media player instance may choose to ignore this for resources + // that have already been opened. + bool Valid() const; + + // Virtual so we can override it for testing. + virtual ResourceMultiBuffer* multibuffer(); + + // Accessor + blink::WebFrame* frame() const { return frame_; } + + protected: + UrlData(const GURL& url, + CORSMode cors_mode, + const base::WeakPtr<UrlIndex>& url_index); + virtual ~UrlData(); + + private: + friend class ResourceMultiBuffer; + friend class UrlIndex; + friend class base::RefCounted<UrlData>; + + void OnEmpty(); + void MergeFrom(const scoped_refptr<UrlData>& other); + + // Url we represent, note that there may be multiple UrlData for + // the same url. + const GURL url_; + + // Cross-origin access mode. + const CORSMode cors_mode_; + + base::WeakPtr<UrlIndex> url_index_; + + // Length of resource this url points to. (in bytes) + int64 length_; + + // Does the server support ranges? + bool range_supported_; + + // Set to false if we have reason to beleive the chrome disk cache + // will not cache this url. + bool cacheable_; + + // Last time some media time used this resource. + // Note that we use base::Time rather than base::TimeTicks because + // TimeTicks will stop advancing when a machine goes to sleep. + // base::Time can go backwards, jump hours at a time and be generally + // unpredictable, but it doesn't stop, which is preferable here. + // (False negatives are better than false positivies.) + base::Time last_used_; + + // Expiration time according to http headers. + base::Time valid_until_; + + // Last modification time according to http headers. + base::Time last_modified_; + + ResourceMultiBuffer multibuffer_; + std::vector<RedirectCB> redirect_callbacks_; + + blink::WebFrame* frame_; + + base::ThreadChecker thread_checker_; + DISALLOW_COPY_AND_ASSIGN(UrlData); +}; + +// The UrlIndex lets you look up UrlData instances by url. +class MEDIA_BLINK_EXPORT UrlIndex { + public: + explicit UrlIndex(blink::WebFrame*); + UrlIndex(blink::WebFrame*, int block_shift); + virtual ~UrlIndex(); + + // Look up an UrlData in the index and return it. If none is found, + // create a new one. Note that newly created UrlData entries are NOT + // added to the index, instead you must call TryInsert on them after + // initializing relevant parameters, like whether it support + // ranges and it's last modified time. + scoped_refptr<UrlData> GetByUrl(const GURL& gurl, + UrlData::CORSMode cors_mode); + + // Add the given UrlData to the index if possible. If a better UrlData + // is already present in the index, return it instead. (If not, we just + // return the given UrlData.) Please make sure to initialize all the data + // that can be gathered from HTTP headers in |url_data| before calling this. + // In particular, the following fields are important: + // o range_supported: Entries which do not support ranges cannot be + // shared and are not added to the index. + // o valid_until, last_used: Entries have to be valid to be inserted + // into the index, this means that they have to have been recently + // used or have an Expires: header that says when they stop being valid. + // o last_modified: Expired cache entries can be re-used if last_modified + // matches. + // TODO(hubbe): Add etag support. + scoped_refptr<UrlData> TryInsert(const scoped_refptr<UrlData>& url_data); + + blink::WebFrame* frame() const { return frame_; } + + private: + friend class UrlData; + friend class ResourceMultiBuffer; + void RemoveUrlDataIfEmpty(const scoped_refptr<UrlData>& url_data); + + // Virtual so we can override it in tests. + virtual scoped_refptr<UrlData> NewUrlData(const GURL& url, + UrlData::CORSMode cors_mode); + + std::map<UrlData::KeyType, scoped_refptr<UrlData>> by_url_; + blink::WebFrame* frame_; + scoped_refptr<MultiBuffer::GlobalLRU> lru_; + bool prune_cb_active_; + + // log2 of block size in multibuffer cache. Defaults to kBlockSizeShift. + // Currently only changed for testing purposes. + const int block_shift_; + + protected: + base::WeakPtrFactory<UrlIndex> weak_factory_; +}; + +} // namespace media +#endif // MEDIA_BLINK_URL_INDEX_H_ diff --git a/media/blink/url_index_unittest.cc b/media/blink/url_index_unittest.cc new file mode 100644 index 0000000..9a309cb --- /dev/null +++ b/media/blink/url_index_unittest.cc @@ -0,0 +1,156 @@ +// Copyright 2015 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 <list> +#include <string> + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "media/blink/url_index.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +class UrlIndexTest : public testing::Test { + public: + UrlIndexTest() : url_index_(nullptr) {} + + scoped_refptr<UrlData> GetByUrl(const GURL& gurl, + UrlData::CORSMode cors_mode) { + scoped_refptr<UrlData> ret = url_index_.GetByUrl(gurl, cors_mode); + EXPECT_EQ(ret->url(), gurl); + EXPECT_EQ(ret->cors_mode(), cors_mode); + return ret; + } + + UrlIndex url_index_; +}; + +TEST_F(UrlIndexTest, SimpleTest) { + GURL url1("http://foo.bar.com"); + GURL url2("http://foo.bar.com/urgel"); + scoped_refptr<UrlData> a = GetByUrl(url1, UrlData::CORS_UNSPECIFIED); + // Make sure it's valid, we still shouldn't get the same one. + a->Use(); + a->set_range_supported(); + EXPECT_TRUE(a->Valid()); + EXPECT_EQ(a, url_index_.TryInsert(a)); + scoped_refptr<UrlData> b = GetByUrl(url1, UrlData::CORS_ANONYMOUS); + b->Use(); + b->set_range_supported(); + EXPECT_TRUE(b->Valid()); + EXPECT_EQ(b, url_index_.TryInsert(b)); + scoped_refptr<UrlData> c = GetByUrl(url1, UrlData::CORS_USE_CREDENTIALS); + c->Use(); + c->set_range_supported(); + EXPECT_TRUE(c->Valid()); + EXPECT_EQ(c, url_index_.TryInsert(c)); + scoped_refptr<UrlData> d = GetByUrl(url2, UrlData::CORS_UNSPECIFIED); + d->Use(); + d->set_range_supported(); + EXPECT_TRUE(d->Valid()); + EXPECT_EQ(d, url_index_.TryInsert(d)); + + EXPECT_NE(a, b); + EXPECT_NE(a, c); + EXPECT_NE(a, d); + EXPECT_NE(b, c); + EXPECT_NE(b, d); + EXPECT_NE(c, d); + EXPECT_EQ(a, GetByUrl(url1, UrlData::CORS_UNSPECIFIED)); + EXPECT_EQ(b, GetByUrl(url1, UrlData::CORS_ANONYMOUS)); + EXPECT_EQ(c, GetByUrl(url1, UrlData::CORS_USE_CREDENTIALS)); + EXPECT_EQ(d, GetByUrl(url2, UrlData::CORS_UNSPECIFIED)); +} + +TEST_F(UrlIndexTest, UrlDataTest) { + GURL url("http://foo.bar.com"); + scoped_refptr<UrlData> a = GetByUrl(url, UrlData::CORS_UNSPECIFIED); + + // Check default values. + EXPECT_FALSE(a->range_supported()); + EXPECT_FALSE(a->cacheable()); + EXPECT_EQ(a->length(), kPositionNotSpecified); + EXPECT_FALSE(a->Valid()); + + a->set_length(117); + EXPECT_EQ(a->length(), 117); + + a->set_cacheable(true); + EXPECT_TRUE(a->cacheable()); + + base::Time now = base::Time::Now(); + base::Time valid_until = now + base::TimeDelta::FromSeconds(500); + a->set_valid_until(valid_until); + a->set_range_supported(); + EXPECT_EQ(valid_until, a->valid_until()); + EXPECT_TRUE(a->Valid()); + + base::Time last_modified = now - base::TimeDelta::FromSeconds(500); + a->set_last_modified(last_modified); + EXPECT_EQ(last_modified, a->last_modified()); +} + +TEST_F(UrlIndexTest, UseTest) { + GURL url("http://foo.bar.com"); + scoped_refptr<UrlData> a = GetByUrl(url, UrlData::CORS_UNSPECIFIED); + EXPECT_FALSE(a->Valid()); + a->Use(); + a->set_range_supported(); + EXPECT_TRUE(a->Valid()); +} + +TEST_F(UrlIndexTest, TryInsert) { + GURL url("http://foo.bar.com"); + scoped_refptr<UrlData> a = GetByUrl(url, UrlData::CORS_UNSPECIFIED); + scoped_refptr<UrlData> c = GetByUrl(url, UrlData::CORS_UNSPECIFIED); + EXPECT_NE(a, c); + EXPECT_FALSE(a->Valid()); + base::Time now = base::Time::Now(); + base::Time last_modified = now - base::TimeDelta::FromSeconds(500); + base::Time valid_until = now + base::TimeDelta::FromSeconds(500); + + // Not sharable yet. (no ranges) + EXPECT_EQ(a, url_index_.TryInsert(a)); + EXPECT_NE(a, GetByUrl(url, UrlData::CORS_UNSPECIFIED)); + a->set_last_modified(last_modified); + + // Not sharable yet. (no ranges) + EXPECT_EQ(a, url_index_.TryInsert(a)); + EXPECT_NE(a, GetByUrl(url, UrlData::CORS_UNSPECIFIED)); + + // Now we should be able to insert it into the index. + a->set_range_supported(); + a->set_valid_until(valid_until); + EXPECT_TRUE(a->Valid()); + EXPECT_EQ(a, url_index_.TryInsert(a)); + EXPECT_EQ(a, GetByUrl(url, UrlData::CORS_UNSPECIFIED)); + + // |a| becomes expired... + a->set_valid_until(now - base::TimeDelta::FromSeconds(100)); + EXPECT_FALSE(a->Valid()); + scoped_refptr<UrlData> b = GetByUrl(url, UrlData::CORS_UNSPECIFIED); + EXPECT_NE(a, b); + + b->set_range_supported(); + b->set_valid_until(valid_until); + b->set_last_modified(last_modified); + EXPECT_TRUE(b->Valid()); + + EXPECT_EQ(b, url_index_.TryInsert(b)); + EXPECT_EQ(b, GetByUrl(url, UrlData::CORS_UNSPECIFIED)); + + c->set_range_supported(); + c->set_valid_until(valid_until); + c->set_last_modified(last_modified); + EXPECT_TRUE(c->Valid()); + + // B is still valid, so it should be preferred over C. + EXPECT_EQ(b, url_index_.TryInsert(c)); + EXPECT_EQ(b, GetByUrl(url, UrlData::CORS_UNSPECIFIED)); +} + +} // namespace media |