1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "media/blink/url_index.h"
6 
7 #include <set>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/feature_list.h"
12 #include "base/location.h"
13 #include "base/single_thread_task_runner.h"
14 #include "base/threading/thread_task_runner_handle.h"
15 #include "base/time/time.h"
16 #include "media/base/media_switches.h"
17 #include "media/blink/resource_multibuffer_data_provider.h"
18 
19 namespace media {
20 
21 const int kBlockSizeShift = 15;  // 1<<15 == 32kb
22 const int kUrlMappingTimeoutSeconds = 300;
23 
ResourceMultiBuffer(UrlData * url_data,int block_shift)24 ResourceMultiBuffer::ResourceMultiBuffer(UrlData* url_data, int block_shift)
25     : MultiBuffer(block_shift, url_data->url_index_->lru_),
26       url_data_(url_data) {}
27 
28 ResourceMultiBuffer::~ResourceMultiBuffer() = default;
29 
CreateWriter(const MultiBufferBlockId & pos,bool is_client_audio_element)30 std::unique_ptr<MultiBuffer::DataProvider> ResourceMultiBuffer::CreateWriter(
31     const MultiBufferBlockId& pos,
32     bool is_client_audio_element) {
33   auto writer = std::make_unique<ResourceMultiBufferDataProvider>(
34       url_data_, pos, is_client_audio_element);
35   writer->Start();
36   return writer;
37 }
38 
RangeSupported() const39 bool ResourceMultiBuffer::RangeSupported() const {
40   return url_data_->range_supported_;
41 }
42 
OnEmpty()43 void ResourceMultiBuffer::OnEmpty() {
44   url_data_->OnEmpty();
45 }
46 
UrlData(const GURL & url,CorsMode cors_mode,UrlIndex * url_index)47 UrlData::UrlData(const GURL& url, CorsMode cors_mode, UrlIndex* url_index)
48     : url_(url),
49       have_data_origin_(false),
50       cors_mode_(cors_mode),
51       has_access_control_(false),
52       url_index_(url_index),
53       length_(kPositionNotSpecified),
54       range_supported_(false),
55       cacheable_(false),
56       last_used_(),
57       multibuffer_(this, url_index_->block_shift_) {}
58 
59 UrlData::~UrlData() = default;
60 
key() const61 std::pair<GURL, UrlData::CorsMode> UrlData::key() const {
62   DCHECK(thread_checker_.CalledOnValidThread());
63   return std::make_pair(url(), cors_mode());
64 }
65 
set_valid_until(base::Time valid_until)66 void UrlData::set_valid_until(base::Time valid_until) {
67   DCHECK(thread_checker_.CalledOnValidThread());
68   valid_until_ = valid_until;
69 }
70 
MergeFrom(const scoped_refptr<UrlData> & other)71 void UrlData::MergeFrom(const scoped_refptr<UrlData>& other) {
72   // We're merging from another UrlData that refers to the *same*
73   // resource, so when we merge the metadata, we can use the most
74   // optimistic values.
75   if (ValidateDataOrigin(other->data_origin_)) {
76     DCHECK(thread_checker_.CalledOnValidThread());
77     valid_until_ = std::max(valid_until_, other->valid_until_);
78     // set_length() will not override the length if already known.
79     set_length(other->length_);
80     cacheable_ |= other->cacheable_;
81     range_supported_ |= other->range_supported_;
82     if (last_modified_.is_null()) {
83       last_modified_ = other->last_modified_;
84     }
85     bytes_read_from_cache_ += other->bytes_read_from_cache_;
86     // is_cors_corss_origin_ will not relax from true to false.
87     set_is_cors_cross_origin(other->is_cors_cross_origin_);
88     has_access_control_ |= other->has_access_control_;
89     multibuffer()->MergeFrom(other->multibuffer());
90   }
91 }
92 
set_cacheable(bool cacheable)93 void UrlData::set_cacheable(bool cacheable) {
94   DCHECK(thread_checker_.CalledOnValidThread());
95   cacheable_ = cacheable;
96 }
97 
set_length(int64_t length)98 void UrlData::set_length(int64_t length) {
99   DCHECK(thread_checker_.CalledOnValidThread());
100   if (length != kPositionNotSpecified) {
101     length_ = length;
102   }
103 }
104 
set_is_cors_cross_origin(bool is_cors_cross_origin)105 void UrlData::set_is_cors_cross_origin(bool is_cors_cross_origin) {
106   if (is_cors_cross_origin_)
107     return;
108   is_cors_cross_origin_ = is_cors_cross_origin;
109 }
110 
set_has_access_control()111 void UrlData::set_has_access_control() {
112   has_access_control_ = true;
113 }
114 
RedirectTo(const scoped_refptr<UrlData> & url_data)115 void UrlData::RedirectTo(const scoped_refptr<UrlData>& url_data) {
116   DCHECK(thread_checker_.CalledOnValidThread());
117   // Copy any cached data over to the new location.
118   url_data->multibuffer()->MergeFrom(multibuffer());
119 
120   std::vector<RedirectCB> redirect_callbacks;
121   redirect_callbacks.swap(redirect_callbacks_);
122   for (RedirectCB& cb : redirect_callbacks) {
123     std::move(cb).Run(url_data);
124   }
125 }
126 
Fail()127 void UrlData::Fail() {
128   DCHECK(thread_checker_.CalledOnValidThread());
129   // Handled similar to a redirect.
130   std::vector<RedirectCB> redirect_callbacks;
131   redirect_callbacks.swap(redirect_callbacks_);
132   for (RedirectCB& cb : redirect_callbacks) {
133     std::move(cb).Run(nullptr);
134   }
135 }
136 
OnRedirect(RedirectCB cb)137 void UrlData::OnRedirect(RedirectCB cb) {
138   DCHECK(thread_checker_.CalledOnValidThread());
139   redirect_callbacks_.push_back(std::move(cb));
140 }
141 
Use()142 void UrlData::Use() {
143   DCHECK(thread_checker_.CalledOnValidThread());
144   last_used_ = base::Time::Now();
145 }
146 
ValidateDataOrigin(const GURL & origin)147 bool UrlData::ValidateDataOrigin(const GURL& origin) {
148   if (!have_data_origin_) {
149     data_origin_ = origin;
150     have_data_origin_ = true;
151     return true;
152   }
153   if (cors_mode_ == UrlData::CORS_UNSPECIFIED) {
154     return data_origin_ == origin;
155   }
156   // The actual cors checks is done in the net layer.
157   return true;
158 }
159 
OnEmpty()160 void UrlData::OnEmpty() {
161   DCHECK(thread_checker_.CalledOnValidThread());
162   url_index_->RemoveUrlData(this);
163 }
164 
FullyCached()165 bool UrlData::FullyCached() {
166   DCHECK(thread_checker_.CalledOnValidThread());
167   if (length_ == kPositionNotSpecified)
168     return false;
169   // Check that the first unavailable block in the cache is after the
170   // end of the file.
171   return (multibuffer()->FindNextUnavailable(0) << kBlockSizeShift) >= length_;
172 }
173 
Valid()174 bool UrlData::Valid() {
175   DCHECK(thread_checker_.CalledOnValidThread());
176   base::Time now = base::Time::Now();
177   if (!range_supported_ && !FullyCached())
178     return false;
179   // When ranges are not supported, we cannot re-use cached data.
180   if (valid_until_ > now)
181     return true;
182   if (now - last_used_ <
183       base::TimeDelta::FromSeconds(kUrlMappingTimeoutSeconds))
184     return true;
185   return false;
186 }
187 
set_last_modified(base::Time last_modified)188 void UrlData::set_last_modified(base::Time last_modified) {
189   DCHECK(thread_checker_.CalledOnValidThread());
190   last_modified_ = last_modified;
191 }
192 
set_etag(const std::string & etag)193 void UrlData::set_etag(const std::string& etag) {
194   DCHECK(thread_checker_.CalledOnValidThread());
195   etag_ = etag;
196 }
197 
set_range_supported()198 void UrlData::set_range_supported() {
199   DCHECK(thread_checker_.CalledOnValidThread());
200   range_supported_ = true;
201 }
202 
multibuffer()203 ResourceMultiBuffer* UrlData::multibuffer() {
204   DCHECK(thread_checker_.CalledOnValidThread());
205   return &multibuffer_;
206 }
207 
CachedSize()208 size_t UrlData::CachedSize() {
209   DCHECK(thread_checker_.CalledOnValidThread());
210   return multibuffer()->map().size();
211 }
212 
UrlIndex(ResourceFetchContext * fetch_context)213 UrlIndex::UrlIndex(ResourceFetchContext* fetch_context)
214     : UrlIndex(fetch_context, kBlockSizeShift) {}
215 
UrlIndex(ResourceFetchContext * fetch_context,int block_shift)216 UrlIndex::UrlIndex(ResourceFetchContext* fetch_context, int block_shift)
217     : fetch_context_(fetch_context),
218       lru_(new MultiBuffer::GlobalLRU(base::ThreadTaskRunnerHandle::Get())),
219       block_shift_(block_shift),
220       memory_pressure_listener_(FROM_HERE,
221                                 base::BindRepeating(&UrlIndex::OnMemoryPressure,
222                                                     base::Unretained(this))) {}
223 
~UrlIndex()224 UrlIndex::~UrlIndex() {
225 #if DCHECK_IS_ON()
226   // Verify that only |this| holds reference to UrlData instances.
227   auto dcheck_has_one_ref = [](const UrlDataMap::value_type& entry) {
228     DCHECK(entry.second->HasOneRef());
229   };
230   std::for_each(indexed_data_.begin(), indexed_data_.end(), dcheck_has_one_ref);
231 #endif
232 }
233 
RemoveUrlData(const scoped_refptr<UrlData> & url_data)234 void UrlIndex::RemoveUrlData(const scoped_refptr<UrlData>& url_data) {
235   DCHECK(url_data->multibuffer()->map().empty());
236 
237   auto i = indexed_data_.find(url_data->key());
238   if (i != indexed_data_.end() && i->second == url_data)
239     indexed_data_.erase(i);
240 }
241 
GetByUrl(const GURL & gurl,UrlData::CorsMode cors_mode)242 scoped_refptr<UrlData> UrlIndex::GetByUrl(const GURL& gurl,
243                                           UrlData::CorsMode cors_mode) {
244   auto i = indexed_data_.find(std::make_pair(gurl, cors_mode));
245   if (i != indexed_data_.end() && i->second->Valid()) {
246     return i->second;
247   }
248 
249   return NewUrlData(gurl, cors_mode);
250 }
251 
NewUrlData(const GURL & url,UrlData::CorsMode cors_mode)252 scoped_refptr<UrlData> UrlIndex::NewUrlData(const GURL& url,
253                                             UrlData::CorsMode cors_mode) {
254   return new UrlData(url, cors_mode, this);
255 }
256 
OnMemoryPressure(base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level)257 void UrlIndex::OnMemoryPressure(
258     base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
259   switch (memory_pressure_level) {
260     case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
261       break;
262     case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
263       lru_->TryFree(128);  // try to free 128 32kb blocks if possible
264       break;
265     case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
266       lru_->TryFreeAll();  // try to free as many blocks as possible
267       break;
268   }
269 }
270 
271 namespace {
IsStrongEtag(const std::string & etag)272 bool IsStrongEtag(const std::string& etag) {
273   return etag.size() > 2 && etag[0] == '"';
274 }
275 
IsNewDataForSameResource(const scoped_refptr<UrlData> & new_entry,const scoped_refptr<UrlData> & old_entry)276 bool IsNewDataForSameResource(const scoped_refptr<UrlData>& new_entry,
277                               const scoped_refptr<UrlData>& old_entry) {
278   if (IsStrongEtag(new_entry->etag()) && IsStrongEtag(old_entry->etag())) {
279     if (new_entry->etag() != old_entry->etag())
280       return true;
281   }
282   if (!new_entry->last_modified().is_null()) {
283     if (new_entry->last_modified() != old_entry->last_modified())
284       return true;
285   }
286   return false;
287 }
288 }  // namespace
289 
TryInsert(const scoped_refptr<UrlData> & url_data)290 scoped_refptr<UrlData> UrlIndex::TryInsert(
291     const scoped_refptr<UrlData>& url_data) {
292   auto iter = indexed_data_.find(url_data->key());
293   if (iter == indexed_data_.end()) {
294     // If valid and not already indexed, index it.
295     if (url_data->Valid()) {
296       indexed_data_.insert(iter, std::make_pair(url_data->key(), url_data));
297     }
298     return url_data;
299   }
300 
301   // A UrlData instance for the same key is already indexed.
302 
303   // If the indexed instance is the same as |url_data|,
304   // nothing needs to be done.
305   if (iter->second == url_data)
306     return url_data;
307 
308   // The indexed instance is different.
309   // Check if it should be replaced with |url_data|.
310   if (IsNewDataForSameResource(url_data, iter->second)) {
311     if (url_data->Valid()) {
312       iter->second = url_data;
313     }
314     return url_data;
315   }
316 
317   if (url_data->Valid()) {
318     if ((!iter->second->Valid() ||
319          url_data->CachedSize() > iter->second->CachedSize())) {
320       iter->second = url_data;
321     } else {
322       iter->second->MergeFrom(url_data);
323     }
324   }
325   return iter->second;
326 }
327 
328 }  // namespace media
329