content/media/mediasource/MediaSource.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

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

mercurial