Bug 1685822 [wpt PR 27117] - [Import Maps] Add tests for rejecting multiple import...
[gecko.git] / dom / media / MediaResource.cpp
blobfeb0b130bf706196344a10d6a19aa47428824c04
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MediaResource.h"
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/Logging.h"
10 #include "mozilla/MathAlgorithms.h"
11 #include "mozilla/ErrorNames.h"
12 #include "mozilla/SchedulerGroup.h"
14 using mozilla::media::TimeUnit;
16 #undef ILOG
18 mozilla::LazyLogModule gMediaResourceIndexLog("MediaResourceIndex");
19 // Debug logging macro with object pointer and class name.
20 #define ILOG(msg, ...) \
21 DDMOZ_LOG(gMediaResourceIndexLog, mozilla::LogLevel::Debug, msg, \
22 ##__VA_ARGS__)
24 namespace mozilla {
26 static const uint32_t kMediaResourceIndexCacheSize = 8192;
27 static_assert(IsPowerOfTwo(kMediaResourceIndexCacheSize),
28 "kMediaResourceIndexCacheSize cache size must be a power of 2");
30 MediaResourceIndex::MediaResourceIndex(MediaResource* aResource)
31 : mResource(aResource),
32 mOffset(0),
33 mCacheBlockSize(
34 aResource->ShouldCacheReads() ? kMediaResourceIndexCacheSize : 0),
35 mCachedOffset(0),
36 mCachedBytes(0),
37 mCachedBlock(MakeUnique<char[]>(mCacheBlockSize)) {
38 DDLINKCHILD("resource", aResource);
41 nsresult MediaResourceIndex::Read(char* aBuffer, uint32_t aCount,
42 uint32_t* aBytes) {
43 NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
45 // We purposefuly don't check that we may attempt to read past
46 // mResource->GetLength() as the resource's length may change over time.
48 nsresult rv = ReadAt(mOffset, aBuffer, aCount, aBytes);
49 if (NS_FAILED(rv)) {
50 return rv;
52 mOffset += *aBytes;
53 if (mOffset < 0) {
54 // Very unlikely overflow; just return to position 0.
55 mOffset = 0;
57 return NS_OK;
60 static nsCString ResultName(nsresult aResult) {
61 nsCString name;
62 GetErrorName(aResult, name);
63 return name;
66 nsresult MediaResourceIndex::ReadAt(int64_t aOffset, char* aBuffer,
67 uint32_t aCount, uint32_t* aBytes) {
68 if (mCacheBlockSize == 0) {
69 return UncachedReadAt(aOffset, aBuffer, aCount, aBytes);
72 *aBytes = 0;
74 if (aCount == 0) {
75 return NS_OK;
78 const int64_t endOffset = aOffset + aCount;
79 if (aOffset < 0 || endOffset < aOffset) {
80 return NS_ERROR_ILLEGAL_VALUE;
83 const int64_t lastBlockOffset = CacheOffsetContaining(endOffset - 1);
85 if (mCachedBytes != 0 && mCachedOffset + mCachedBytes >= aOffset &&
86 mCachedOffset < endOffset) {
87 // There is data in the cache that is not completely before aOffset and not
88 // completely after endOffset, so it could be usable (with potential
89 // top-up).
90 if (aOffset < mCachedOffset) {
91 // We need to read before the cached data.
92 const uint32_t toRead = uint32_t(mCachedOffset - aOffset);
93 MOZ_ASSERT(toRead > 0);
94 MOZ_ASSERT(toRead < aCount);
95 uint32_t read = 0;
96 nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
97 if (NS_FAILED(rv)) {
98 ILOG("ReadAt(%" PRIu32 "@%" PRId64
99 ") uncached read before cache -> %s, %" PRIu32,
100 aCount, aOffset, ResultName(rv).get(), *aBytes);
101 return rv;
103 *aBytes = read;
104 if (read < toRead) {
105 // Could not read everything we wanted, we're done.
106 ILOG("ReadAt(%" PRIu32 "@%" PRId64
107 ") uncached read before cache, incomplete -> OK, %" PRIu32,
108 aCount, aOffset, *aBytes);
109 return NS_OK;
111 ILOG("ReadAt(%" PRIu32 "@%" PRId64
112 ") uncached read before cache: %" PRIu32 ", remaining: %" PRIu32
113 "@%" PRId64 "...",
114 aCount, aOffset, read, aCount - read, aOffset + read);
115 aOffset += read;
116 aBuffer += read;
117 aCount -= read;
118 // We should have reached the cache.
119 MOZ_ASSERT(aOffset == mCachedOffset);
121 MOZ_ASSERT(aOffset >= mCachedOffset);
123 // We've reached our cache.
124 const uint32_t toCopy =
125 std::min(aCount, uint32_t(mCachedOffset + mCachedBytes - aOffset));
126 // Note that we could in fact be just after the last byte of the cache, in
127 // which case we can't actually read from it! (But we will top-up next.)
128 if (toCopy != 0) {
129 memcpy(aBuffer, &mCachedBlock[IndexInCache(aOffset)], toCopy);
130 *aBytes += toCopy;
131 aCount -= toCopy;
132 if (aCount == 0) {
133 // All done!
134 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied everything (%" PRIu32
135 ") from cache(%" PRIu32 "@%" PRId64 ") :-D -> OK, %" PRIu32,
136 aCount, aOffset, toCopy, mCachedBytes, mCachedOffset, *aBytes);
137 return NS_OK;
139 aOffset += toCopy;
140 aBuffer += toCopy;
141 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") copied %" PRIu32
142 " from cache(%" PRIu32 "@%" PRId64 ") :-), remaining: %" PRIu32
143 "@%" PRId64 "...",
144 aCount + toCopy, aOffset - toCopy, toCopy, mCachedBytes,
145 mCachedOffset, aCount, aOffset);
148 if (aOffset - 1 >= lastBlockOffset) {
149 // We were already reading cached data from the last block, we need more
150 // from it -> try to top-up, read what we can, and we'll be done.
151 MOZ_ASSERT(aOffset == mCachedOffset + mCachedBytes);
152 MOZ_ASSERT(endOffset <= lastBlockOffset + mCacheBlockSize);
153 return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
156 // We were not in the last block (but we may just have crossed the line now)
157 MOZ_ASSERT(aOffset <= lastBlockOffset);
158 // Continue below...
159 } else if (aOffset >= lastBlockOffset) {
160 // There was nothing we could get from the cache.
161 // But we're already in the last block -> Cache or read what we can.
162 // Make sure to invalidate the cache first.
163 mCachedBytes = 0;
164 return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
167 // If we're here, either there was nothing usable in the cache, or we've just
168 // read what was in the cache but there's still more to read.
170 if (aOffset < lastBlockOffset) {
171 // We need to read before the last block.
172 // Start with an uncached read up to the last block.
173 const uint32_t toRead = uint32_t(lastBlockOffset - aOffset);
174 MOZ_ASSERT(toRead > 0);
175 MOZ_ASSERT(toRead < aCount);
176 uint32_t read = 0;
177 nsresult rv = UncachedReadAt(aOffset, aBuffer, toRead, &read);
178 if (NS_FAILED(rv)) {
179 ILOG("ReadAt(%" PRIu32 "@%" PRId64
180 ") uncached read before last block failed -> %s, %" PRIu32,
181 aCount, aOffset, ResultName(rv).get(), *aBytes);
182 return rv;
184 if (read == 0) {
185 ILOG("ReadAt(%" PRIu32 "@%" PRId64
186 ") uncached read 0 before last block -> OK, %" PRIu32,
187 aCount, aOffset, *aBytes);
188 return NS_OK;
190 *aBytes += read;
191 if (read < toRead) {
192 // Could not read everything we wanted, we're done.
193 ILOG("ReadAt(%" PRIu32 "@%" PRId64
194 ") uncached read before last block, incomplete -> OK, %" PRIu32,
195 aCount, aOffset, *aBytes);
196 return NS_OK;
198 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") read %" PRIu32
199 " before last block, remaining: %" PRIu32 "@%" PRId64 "...",
200 aCount, aOffset, read, aCount - read, aOffset + read);
201 aOffset += read;
202 aBuffer += read;
203 aCount -= read;
206 // We should just have reached the start of the last block.
207 MOZ_ASSERT(aOffset == lastBlockOffset);
208 MOZ_ASSERT(aCount <= mCacheBlockSize);
209 // Make sure to invalidate the cache first.
210 mCachedBytes = 0;
211 return CacheOrReadAt(aOffset, aBuffer, aCount, aBytes);
214 nsresult MediaResourceIndex::CacheOrReadAt(int64_t aOffset, char* aBuffer,
215 uint32_t aCount, uint32_t* aBytes) {
216 // We should be here because there is more data to read.
217 MOZ_ASSERT(aCount > 0);
218 // We should be in the last block, so we shouldn't try to read past it.
219 MOZ_ASSERT(IndexInCache(aOffset) + aCount <= mCacheBlockSize);
221 const int64_t length = GetLength();
222 // If length is unknown (-1), look at resource-cached data.
223 // If length is known and equal or greater than requested, also look at
224 // resource-cached data.
225 // Otherwise, if length is known but same, or less than(!?), requested, don't
226 // attempt to access resource-cached data, as we're not expecting it to ever
227 // be greater than the length.
228 if (length < 0 || length >= aOffset + aCount) {
229 // Is there cached data covering at least the requested range?
230 const int64_t cachedDataEnd = mResource->GetCachedDataEnd(aOffset);
231 if (cachedDataEnd >= aOffset + aCount) {
232 // Try to read as much resource-cached data as can fill our local cache.
233 // Assume we can read as much as is cached without blocking.
234 const uint32_t cacheIndex = IndexInCache(aOffset);
235 const uint32_t toRead = uint32_t(std::min(
236 cachedDataEnd - aOffset, int64_t(mCacheBlockSize - cacheIndex)));
237 MOZ_ASSERT(toRead >= aCount);
238 uint32_t read = 0;
239 // We would like `toRead` if possible, but ok with at least `aCount`.
240 nsresult rv = UncachedRangedReadAt(aOffset, &mCachedBlock[cacheIndex],
241 aCount, toRead - aCount, &read);
242 if (NS_SUCCEEDED(rv)) {
243 if (read == 0) {
244 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
245 "..%" PRIu32 "@%" PRId64
246 ") to top-up succeeded but read nothing -> OK anyway",
247 aCount, aOffset, aCount, toRead, aOffset);
248 // Couldn't actually read anything, but didn't error out, so count
249 // that as success.
250 return NS_OK;
252 if (mCachedOffset + mCachedBytes == aOffset) {
253 // We were topping-up the cache, just update its size.
254 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
255 "..%" PRIu32 "@%" PRId64 ") to top-up succeeded to read %" PRIu32
256 "...",
257 aCount, aOffset, aCount, toRead, aOffset, read);
258 mCachedBytes += read;
259 } else {
260 // We were filling the cache from scratch, save new cache information.
261 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
262 "..%" PRIu32 "@%" PRId64
263 ") to fill cache succeeded to read %" PRIu32 "...",
264 aCount, aOffset, aCount, toRead, aOffset, read);
265 mCachedOffset = aOffset;
266 mCachedBytes = read;
268 // Copy relevant part into output.
269 uint32_t toCopy = std::min(aCount, read);
270 memcpy(aBuffer, &mCachedBlock[cacheIndex], toCopy);
271 *aBytes += toCopy;
272 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - copied %" PRIu32 "@%" PRId64
273 " -> OK, %" PRIu32,
274 aCount, aOffset, toCopy, aOffset, *aBytes);
275 // We may not have read all that was requested, but we got everything
276 // we could get, so we're done.
277 return NS_OK;
279 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - UncachedRangedReadAt(%" PRIu32
280 "..%" PRIu32 "@%" PRId64
281 ") failed: %s, will fallback to blocking read...",
282 aCount, aOffset, aCount, toRead, aOffset, ResultName(rv).get());
283 // Failure during reading. Note that this may be due to the cache
284 // changing between `GetCachedDataEnd` and `ReadAt`, so it's not
285 // totally unexpected, just hopefully rare; but we do need to handle it.
287 // Invalidate part of cache that may have been partially overridden.
288 if (mCachedOffset + mCachedBytes == aOffset) {
289 // We were topping-up the cache, just keep the old untouched data.
290 // (i.e., nothing to do here.)
291 } else {
292 // We were filling the cache from scratch, invalidate cache.
293 mCachedBytes = 0;
295 } else {
296 ILOG("ReadAt(%" PRIu32 "@%" PRId64
297 ") - no cached data, will fallback to blocking read...",
298 aCount, aOffset);
300 } else {
301 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - length is %" PRId64
302 " (%s), will fallback to blocking read as the caller requested...",
303 aCount, aOffset, length, length < 0 ? "unknown" : "too short!");
305 uint32_t read = 0;
306 nsresult rv = UncachedReadAt(aOffset, aBuffer, aCount, &read);
307 if (NS_SUCCEEDED(rv)) {
308 *aBytes += read;
309 ILOG("ReadAt(%" PRIu32 "@%" PRId64 ") - fallback uncached read got %" PRIu32
310 " bytes -> %s, %" PRIu32,
311 aCount, aOffset, read, ResultName(rv).get(), *aBytes);
312 } else {
313 ILOG("ReadAt(%" PRIu32 "@%" PRId64
314 ") - fallback uncached read failed -> %s, %" PRIu32,
315 aCount, aOffset, ResultName(rv).get(), *aBytes);
317 return rv;
320 nsresult MediaResourceIndex::UncachedReadAt(int64_t aOffset, char* aBuffer,
321 uint32_t aCount,
322 uint32_t* aBytes) const {
323 if (aOffset < 0) {
324 return NS_ERROR_ILLEGAL_VALUE;
326 if (aCount == 0) {
327 *aBytes = 0;
328 return NS_OK;
330 return mResource->ReadAt(aOffset, aBuffer, aCount, aBytes);
333 nsresult MediaResourceIndex::UncachedRangedReadAt(int64_t aOffset,
334 char* aBuffer,
335 uint32_t aRequestedCount,
336 uint32_t aExtraCount,
337 uint32_t* aBytes) const {
338 uint32_t count = aRequestedCount + aExtraCount;
339 if (aOffset < 0 || count < aRequestedCount) {
340 return NS_ERROR_ILLEGAL_VALUE;
342 if (count == 0) {
343 *aBytes = 0;
344 return NS_OK;
346 return mResource->ReadAt(aOffset, aBuffer, count, aBytes);
349 nsresult MediaResourceIndex::Seek(int32_t aWhence, int64_t aOffset) {
350 switch (aWhence) {
351 case SEEK_SET:
352 break;
353 case SEEK_CUR:
354 aOffset += mOffset;
355 break;
356 case SEEK_END: {
357 int64_t length = mResource->GetLength();
358 if (length == -1 || length - aOffset < 0) {
359 return NS_ERROR_FAILURE;
361 aOffset = mResource->GetLength() - aOffset;
362 } break;
363 default:
364 return NS_ERROR_FAILURE;
367 if (aOffset < 0) {
368 return NS_ERROR_ILLEGAL_VALUE;
370 mOffset = aOffset;
372 return NS_OK;
375 already_AddRefed<MediaByteBuffer> MediaResourceIndex::MediaReadAt(
376 int64_t aOffset, uint32_t aCount) const {
377 NS_ENSURE_TRUE(aOffset >= 0, nullptr);
378 RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
379 bool ok = bytes->SetLength(aCount, fallible);
380 NS_ENSURE_TRUE(ok, nullptr);
382 uint32_t bytesRead = 0;
383 nsresult rv = mResource->ReadAt(
384 aOffset, reinterpret_cast<char*>(bytes->Elements()), aCount, &bytesRead);
385 NS_ENSURE_SUCCESS(rv, nullptr);
387 bytes->SetLength(bytesRead);
388 return bytes.forget();
391 already_AddRefed<MediaByteBuffer> MediaResourceIndex::CachedMediaReadAt(
392 int64_t aOffset, uint32_t aCount) const {
393 RefPtr<MediaByteBuffer> bytes = new MediaByteBuffer();
394 bool ok = bytes->SetLength(aCount, fallible);
395 NS_ENSURE_TRUE(ok, nullptr);
396 char* curr = reinterpret_cast<char*>(bytes->Elements());
397 nsresult rv = mResource->ReadFromCache(curr, aOffset, aCount);
398 NS_ENSURE_SUCCESS(rv, nullptr);
399 return bytes.forget();
402 // Get the length of the stream in bytes. Returns -1 if not known.
403 // This can change over time; after a seek operation, a misbehaving
404 // server may give us a resource of a different length to what it had
405 // reported previously --- or it may just lie in its Content-Length
406 // header and give us more or less data than it reported. We will adjust
407 // the result of GetLength to reflect the data that's actually arriving.
408 int64_t MediaResourceIndex::GetLength() const { return mResource->GetLength(); }
410 uint32_t MediaResourceIndex::IndexInCache(int64_t aOffsetInFile) const {
411 const uint32_t index = uint32_t(aOffsetInFile) & (mCacheBlockSize - 1);
412 MOZ_ASSERT(index == aOffsetInFile % mCacheBlockSize);
413 return index;
416 int64_t MediaResourceIndex::CacheOffsetContaining(int64_t aOffsetInFile) const {
417 const int64_t offset = aOffsetInFile & ~(int64_t(mCacheBlockSize) - 1);
418 MOZ_ASSERT(offset == aOffsetInFile - IndexInCache(aOffsetInFile));
419 return offset;
422 } // namespace mozilla
424 // avoid redefined macro in unified build
425 #undef ILOG