Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "MediaSource.h"
9 #include "AsyncEventRunner.h"
10 #include "DecoderTraits.h"
11 #include "SourceBuffer.h"
12 #include "SourceBufferList.h"
13 #include "mozilla/ErrorResult.h"
14 #include "mozilla/FloatingPoint.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/dom/BindingDeclarations.h"
17 #include "mozilla/dom/HTMLMediaElement.h"
18 #include "mozilla/mozalloc.h"
19 #include "nsContentTypeParser.h"
20 #include "nsDebug.h"
21 #include "nsError.h"
22 #include "nsIEventTarget.h"
23 #include "nsIRunnable.h"
24 #include "nsPIDOMWindow.h"
25 #include "nsStringGlue.h"
26 #include "nsThreadUtils.h"
27 #include "prlog.h"
29 struct JSContext;
30 class JSObject;
32 #ifdef PR_LOGGING
33 PRLogModuleInfo* gMediaSourceLog;
34 #define MSE_DEBUG(...) PR_LOG(gMediaSourceLog, PR_LOG_DEBUG, (__VA_ARGS__))
35 #else
36 #define MSE_DEBUG(...)
37 #endif
39 // Arbitrary limit.
40 static const unsigned int MAX_SOURCE_BUFFERS = 16;
42 namespace mozilla {
44 static const char* const gMediaSourceTypes[6] = {
45 "video/webm",
46 "audio/webm",
47 // XXX: Disabled other codecs temporarily to allow WebM testing. For now, set
48 // the developer-only media.mediasource.ignore_codecs pref to true to test other
49 // codecs, and expect things to be broken.
50 #if 0
51 "video/mp4",
52 "audio/mp4",
53 "audio/mpeg",
54 #endif
55 nullptr
56 };
58 static nsresult
59 IsTypeSupported(const nsAString& aType)
60 {
61 if (aType.IsEmpty()) {
62 return NS_ERROR_DOM_INVALID_ACCESS_ERR;
63 }
64 // TODO: Further restrict this to formats in the spec.
65 nsContentTypeParser parser(aType);
66 nsAutoString mimeType;
67 nsresult rv = parser.GetType(mimeType);
68 if (NS_FAILED(rv)) {
69 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
70 }
71 bool found = false;
72 for (uint32_t i = 0; gMediaSourceTypes[i]; ++i) {
73 if (mimeType.EqualsASCII(gMediaSourceTypes[i])) {
74 found = true;
75 break;
76 }
77 }
78 if (!found) {
79 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
80 }
81 if (Preferences::GetBool("media.mediasource.ignore_codecs", false)) {
82 return NS_OK;
83 }
84 // Check aType against HTMLMediaElement list of MIME types. Since we've
85 // already restricted the container format, this acts as a specific check
86 // of any specified "codecs" parameter of aType.
87 if (dom::HTMLMediaElement::GetCanPlay(aType) == CANPLAY_NO) {
88 return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
89 }
90 return NS_OK;
91 }
93 namespace dom {
95 /* static */ already_AddRefed<MediaSource>
96 MediaSource::Constructor(const GlobalObject& aGlobal,
97 ErrorResult& aRv)
98 {
99 nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
100 if (!window) {
101 aRv.Throw(NS_ERROR_UNEXPECTED);
102 return nullptr;
103 }
105 nsRefPtr<MediaSource> mediaSource = new MediaSource(window);
106 return mediaSource.forget();
107 }
109 SourceBufferList*
110 MediaSource::SourceBuffers()
111 {
112 MOZ_ASSERT_IF(mReadyState == MediaSourceReadyState::Closed, mSourceBuffers->IsEmpty());
113 return mSourceBuffers;
114 }
116 SourceBufferList*
117 MediaSource::ActiveSourceBuffers()
118 {
119 MOZ_ASSERT_IF(mReadyState == MediaSourceReadyState::Closed, mActiveSourceBuffers->IsEmpty());
120 return mActiveSourceBuffers;
121 }
123 MediaSourceReadyState
124 MediaSource::ReadyState()
125 {
126 return mReadyState;
127 }
129 double
130 MediaSource::Duration()
131 {
132 if (mReadyState == MediaSourceReadyState::Closed) {
133 return UnspecifiedNaN<double>();
134 }
135 return mDuration;
136 }
138 void
139 MediaSource::SetDuration(double aDuration, ErrorResult& aRv)
140 {
141 if (aDuration < 0 || IsNaN(aDuration)) {
142 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
143 return;
144 }
145 if (mReadyState != MediaSourceReadyState::Open ||
146 mSourceBuffers->AnyUpdating()) {
147 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
148 return;
149 }
150 DurationChange(aDuration, aRv);
151 }
153 already_AddRefed<SourceBuffer>
154 MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
155 {
156 nsresult rv = mozilla::IsTypeSupported(aType);
157 MSE_DEBUG("MediaSource::AddSourceBuffer(Type=%s) -> %x", NS_ConvertUTF16toUTF8(aType).get(), rv);
158 if (NS_FAILED(rv)) {
159 aRv.Throw(rv);
160 return nullptr;
161 }
162 if (mSourceBuffers->Length() >= MAX_SOURCE_BUFFERS) {
163 aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
164 return nullptr;
165 }
166 if (mReadyState != MediaSourceReadyState::Open) {
167 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
168 return nullptr;
169 }
170 nsContentTypeParser parser(aType);
171 nsAutoString mimeType;
172 rv = parser.GetType(mimeType);
173 if (NS_FAILED(rv)) {
174 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
175 return nullptr;
176 }
177 nsRefPtr<SourceBuffer> sourceBuffer = SourceBuffer::Create(this, NS_ConvertUTF16toUTF8(mimeType));
178 if (!sourceBuffer) {
179 aRv.Throw(NS_ERROR_FAILURE); // XXX need a better error here
180 return nullptr;
181 }
182 mSourceBuffers->Append(sourceBuffer);
183 mActiveSourceBuffers->Append(sourceBuffer);
184 MSE_DEBUG("%p AddSourceBuffer(Type=%s) -> %p", this,
185 NS_ConvertUTF16toUTF8(mimeType).get(), sourceBuffer.get());
186 return sourceBuffer.forget();
187 }
189 void
190 MediaSource::RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv)
191 {
192 SourceBuffer* sourceBuffer = &aSourceBuffer;
193 MSE_DEBUG("%p RemoveSourceBuffer(Buffer=%p)", this, sourceBuffer);
194 if (!mSourceBuffers->Contains(sourceBuffer)) {
195 aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR);
196 return;
197 }
198 if (sourceBuffer->Updating()) {
199 // TODO:
200 // abort stream append loop (if running)
201 // set updating to false
202 // fire "abort" at sourceBuffer
203 // fire "updateend" at sourceBuffer
204 }
205 // TODO:
206 // For all sourceBuffer audioTracks, videoTracks, textTracks:
207 // set sourceBuffer to null
208 // remove sourceBuffer video, audio, text Tracks from MediaElement tracks
209 // remove sourceBuffer video, audio, text Tracks and fire "removetrack" at affected lists
210 // fire "removetrack" at modified MediaElement track lists
211 // If removed enabled/selected, fire "change" at affected MediaElement list.
212 if (mActiveSourceBuffers->Contains(sourceBuffer)) {
213 mActiveSourceBuffers->Remove(sourceBuffer);
214 }
215 mSourceBuffers->Remove(sourceBuffer);
216 // TODO: Free all resources associated with sourceBuffer
217 }
219 void
220 MediaSource::EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv)
221 {
222 MSE_DEBUG("%p EndOfStream(Error=%u)", this, aError.WasPassed() ? uint32_t(aError.Value()) : 0);
223 if (mReadyState != MediaSourceReadyState::Open ||
224 mSourceBuffers->AnyUpdating()) {
225 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
226 return;
227 }
229 SetReadyState(MediaSourceReadyState::Ended);
230 mSourceBuffers->Ended();
231 if (!aError.WasPassed()) {
232 // TODO:
233 // Run duration change algorithm.
234 // DurationChange(highestDurationOfSourceBuffers, aRv);
235 // if (aRv.Failed()) {
236 // return;
237 // }
238 // Notify media element that all data is now available.
239 return;
240 }
241 switch (aError.Value()) {
242 case MediaSourceEndOfStreamError::Network:
243 // TODO: If media element has a readyState of:
244 // HAVE_NOTHING -> run resource fetch algorithm
245 // > HAVE_NOTHING -> run "interrupted" steps of resource fetch
246 break;
247 case MediaSourceEndOfStreamError::Decode:
248 // TODO: If media element has a readyState of:
249 // HAVE_NOTHING -> run "unsupported" steps of resource fetch
250 // > HAVE_NOTHING -> run "corrupted" steps of resource fetch
251 break;
252 default:
253 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
254 }
255 }
257 /* static */ bool
258 MediaSource::IsTypeSupported(const GlobalObject&, const nsAString& aType)
259 {
260 #ifdef PR_LOGGING
261 if (!gMediaSourceLog) {
262 gMediaSourceLog = PR_NewLogModule("MediaSource");
263 }
264 #endif
265 nsresult rv = mozilla::IsTypeSupported(aType);
266 MSE_DEBUG("MediaSource::IsTypeSupported(Type=%s) -> %x", NS_ConvertUTF16toUTF8(aType).get(), rv);
267 return NS_SUCCEEDED(rv);
268 }
270 bool
271 MediaSource::Attach(MediaSourceDecoder* aDecoder)
272 {
273 MSE_DEBUG("%p Attaching decoder %p owner %p", this, aDecoder, aDecoder->GetOwner());
274 MOZ_ASSERT(aDecoder);
275 if (mReadyState != MediaSourceReadyState::Closed) {
276 return false;
277 }
278 mDecoder = aDecoder;
279 mDecoder->AttachMediaSource(this);
280 SetReadyState(MediaSourceReadyState::Open);
281 return true;
282 }
284 void
285 MediaSource::Detach()
286 {
287 MSE_DEBUG("%p Detaching decoder %p owner %p", this, mDecoder.get(), mDecoder->GetOwner());
288 MOZ_ASSERT(mDecoder);
289 mDecoder->DetachMediaSource();
290 mDecoder = nullptr;
291 mDuration = UnspecifiedNaN<double>();
292 mActiveSourceBuffers->Clear();
293 mSourceBuffers->Clear();
294 SetReadyState(MediaSourceReadyState::Closed);
295 }
297 MediaSource::MediaSource(nsPIDOMWindow* aWindow)
298 : DOMEventTargetHelper(aWindow)
299 , mDuration(UnspecifiedNaN<double>())
300 , mDecoder(nullptr)
301 , mReadyState(MediaSourceReadyState::Closed)
302 {
303 mSourceBuffers = new SourceBufferList(this);
304 mActiveSourceBuffers = new SourceBufferList(this);
306 #ifdef PR_LOGGING
307 if (!gMediaSourceLog) {
308 gMediaSourceLog = PR_NewLogModule("MediaSource");
309 }
310 #endif
311 }
313 void
314 MediaSource::SetReadyState(MediaSourceReadyState aState)
315 {
316 MOZ_ASSERT(aState != mReadyState);
317 MSE_DEBUG("%p SetReadyState old=%d new=%d", this, mReadyState, aState);
319 MediaSourceReadyState oldState = mReadyState;
320 mReadyState = aState;
322 if (mReadyState == MediaSourceReadyState::Open &&
323 (oldState == MediaSourceReadyState::Closed ||
324 oldState == MediaSourceReadyState::Ended)) {
325 QueueAsyncSimpleEvent("sourceopen");
326 return;
327 }
329 if (mReadyState == MediaSourceReadyState::Ended &&
330 oldState == MediaSourceReadyState::Open) {
331 QueueAsyncSimpleEvent("sourceended");
332 return;
333 }
335 if (mReadyState == MediaSourceReadyState::Closed &&
336 (oldState == MediaSourceReadyState::Open ||
337 oldState == MediaSourceReadyState::Ended)) {
338 QueueAsyncSimpleEvent("sourceclose");
339 return;
340 }
342 NS_WARNING("Invalid MediaSource readyState transition");
343 }
345 void
346 MediaSource::DispatchSimpleEvent(const char* aName)
347 {
348 MSE_DEBUG("%p Dispatching event %s to MediaSource", this, aName);
349 DispatchTrustedEvent(NS_ConvertUTF8toUTF16(aName));
350 }
352 void
353 MediaSource::QueueAsyncSimpleEvent(const char* aName)
354 {
355 MSE_DEBUG("%p Queuing event %s to MediaSource", this, aName);
356 nsCOMPtr<nsIRunnable> event = new AsyncEventRunner<MediaSource>(this, aName);
357 NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
358 }
360 void
361 MediaSource::DurationChange(double aNewDuration, ErrorResult& aRv)
362 {
363 if (mDuration == aNewDuration) {
364 return;
365 }
366 double oldDuration = mDuration;
367 mDuration = aNewDuration;
368 if (aNewDuration < oldDuration) {
369 mSourceBuffers->Remove(aNewDuration, oldDuration, aRv);
370 if (aRv.Failed()) {
371 return;
372 }
373 }
374 // TODO: If partial audio frames/text cues exist, clamp duration based on mSourceBuffers.
375 // TODO: Update media element's duration and run element's duration change algorithm.
376 }
378 nsPIDOMWindow*
379 MediaSource::GetParentObject() const
380 {
381 return GetOwner();
382 }
384 JSObject*
385 MediaSource::WrapObject(JSContext* aCx)
386 {
387 return MediaSourceBinding::Wrap(aCx, this);
388 }
390 void
391 MediaSource::NotifyEvicted(double aStart, double aEnd)
392 {
393 // Cycle through all SourceBuffers and tell them to evict data in
394 // the given range.
395 mSourceBuffers->Evict(aStart, aEnd);
396 }
398 NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaSource, DOMEventTargetHelper,
399 mSourceBuffers, mActiveSourceBuffers)
401 NS_IMPL_ADDREF_INHERITED(MediaSource, DOMEventTargetHelper)
402 NS_IMPL_RELEASE_INHERITED(MediaSource, DOMEventTargetHelper)
404 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MediaSource)
405 NS_INTERFACE_MAP_ENTRY(mozilla::dom::MediaSource)
406 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
408 } // namespace dom
410 } // namespace mozilla