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