Bug 963238: Support isTypeSupported() in MediaRecorder, and throw on invalid mimetypes at construction r=cpearce,khuey
authorRandell Jesup <rjesup@jesup.org>
Sun, 06 Mar 2016 22:48:16 -0500
changeset 287009 4d005bc5a46268596e25a146dd1c172aaaf1ff60
parent 287008 b5bbe0866d72f3231ed7188953985b1104de6aa1
child 287010 30fa1cfd2ec8f5fbde21601f0f5d67406cd625a5
push id18032
push usercbook@mozilla.com
push dateMon, 07 Mar 2016 10:38:51 +0000
treeherderfx-team@087905ffec78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, khuey
bugs963238
milestone47.0a1
Bug 963238: Support isTypeSupported() in MediaRecorder, and throw on invalid mimetypes at construction r=cpearce,khuey MozReview-Commit-ID: LPIJMSgXwxf
dom/media/MediaRecorder.cpp
dom/media/MediaRecorder.h
dom/media/test/mochitest.ini
dom/media/test/test_mediarecorder_mp4_support.html
dom/media/test/test_mediarecorder_unsupported_src.html
dom/media/test/test_mediarecorder_webm_support.html
dom/webidl/MediaRecorder.webidl
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -22,16 +22,18 @@
 #include "nsError.h"
 #include "nsIDocument.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsMimeTypes.h"
 #include "nsProxyRelease.h"
 #include "nsTArray.h"
 #include "GeckoProfiler.h"
+#include "nsContentTypeParser.h"
+#include "nsCharSeparatedTokenizer.h"
 
 #ifdef LOG
 #undef LOG
 #endif
 
 mozilla::LazyLogModule gMediaRecorderLog("MediaRecorder");
 #define LOG(type, msg) MOZ_LOG(gMediaRecorderLog, type, msg)
 
@@ -966,16 +968,21 @@ MediaRecorder::Constructor(const GlobalO
                            ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(aGlobal.GetAsSupports());
   if (!ownerWindow) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
+  if (!IsTypeSupported(aInitDict.mMimeType)) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return nullptr;
+  }
+
   RefPtr<MediaRecorder> object = new MediaRecorder(aStream, ownerWindow);
   object->SetOptions(aInitDict);
   return object.forget();
 }
 
 /* static */ already_AddRefed<MediaRecorder>
 MediaRecorder::Constructor(const GlobalObject& aGlobal,
                            AudioNode& aSrcAudioNode,
@@ -1000,16 +1007,21 @@ MediaRecorder::Constructor(const GlobalO
 
   // aSrcOutput doesn't matter to destination node because it has no output.
   if (aSrcAudioNode.NumberOfOutputs() > 0 &&
        aSrcOutput >= aSrcAudioNode.NumberOfOutputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return nullptr;
   }
 
+  if (!IsTypeSupported(aInitDict.mMimeType)) {
+    aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return nullptr;
+  }
+
   RefPtr<MediaRecorder> object = new MediaRecorder(aSrcAudioNode,
                                                      aSrcOutput,
                                                      ownerWindow);
   object->SetOptions(aInitDict);
   return object.forget();
 }
 
 void
@@ -1030,16 +1042,117 @@ MediaRecorder::SetOptions(const MediaRec
   //
   // Until dynamic changes are supported, I prefer to be safe and err
   // slightly high
   if (aInitDict.mBitsPerSecond.WasPassed() && !aInitDict.mVideoBitsPerSecond.WasPassed()) {
     mVideoBitsPerSecond = mBitsPerSecond;
   }
 }
 
+static char const *const gWebMAudioEncoderCodecs[3] = {
+  "vorbis",
+  "opus",
+  // no VP9 yet
+  nullptr,
+};
+static char const *const gWebMVideoEncoderCodecs[5] = {
+  "vorbis",
+  "opus",
+  "vp8",
+  "vp8.0",
+  // no VP9 yet
+  nullptr,
+};
+static char const *const gOggAudioEncoderCodecs[2] = {
+  "opus",
+  // we could support vorbis here too, but don't
+  nullptr,
+};
+
+template <class String>
+static bool
+CodecListContains(char const *const * aCodecs, const String& aCodec)
+{
+  for (int32_t i = 0; aCodecs[i]; ++i) {
+    if (aCodec.EqualsASCII(aCodecs[i]))
+      return true;
+  }
+  return false;
+}
+
+/* static */
+bool
+MediaRecorder::IsTypeSupported(GlobalObject& aGlobal, const nsAString& aMIMEType)
+{
+  return IsTypeSupported(aMIMEType);
+}
+
+/* static */
+bool
+MediaRecorder::IsTypeSupported(const nsAString& aMIMEType)
+{
+  char const* const* codeclist = nullptr;
+
+  if (aMIMEType.IsEmpty()) {
+    return true;
+  }
+
+  nsContentTypeParser parser(aMIMEType);
+  nsAutoString mimeType;
+  nsresult rv = parser.GetType(mimeType);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  // effectively a 'switch (mimeType) {'
+  if (mimeType.EqualsLiteral(AUDIO_OGG)) {
+    if (MediaDecoder::IsOggEnabled() && MediaDecoder::IsOpusEnabled()) {
+      codeclist = gOggAudioEncoderCodecs;
+    }
+  }
+#ifdef MOZ_WEBM_ENCODER
+  else if (mimeType.EqualsLiteral(VIDEO_WEBM) &&
+           MediaEncoder::IsWebMEncoderEnabled()) {
+    codeclist = gWebMVideoEncoderCodecs;
+  }
+#endif
+#ifdef MOZ_OMX_ENCODER
+    // We're working on MP4 encoder support for desktop
+  else if (mimeType.EqualsLiteral(VIDEO_MP4) ||
+           mimeType.EqualsLiteral(AUDIO_3GPP) ||
+           mimeType.EqualsLiteral(AUDIO_3GPP2)) {
+    if (MediaEncoder::IsOMXEncoderEnabled()) {
+      // XXX check codecs for MP4/3GPP
+      return true;
+    }
+  }
+#endif
+
+  // codecs don't matter if we don't support the container
+  if (!codeclist) {
+    return false;
+  }
+  // now filter on codecs, and if needed rescind support
+  nsAutoString codecstring;
+  rv = parser.GetParameter("codecs", codecstring);
+
+  nsTArray<nsString> codecs;
+  if (!ParseCodecsString(codecstring, codecs)) {
+    return false;
+  }
+  for (const nsString& codec : codecs) {
+    if (!CodecListContains(codeclist, codec)) {
+      // Totally unsupported codec
+      return false;
+    }
+  }
+
+  return true;
+}
+
 nsresult
 MediaRecorder::CreateAndDispatchBlobEvent(already_AddRefed<nsIDOMBlob>&& aBlob)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
   if (!CheckPrincipal()) {
     // Media is not same-origin, don't allow the data out.
     RefPtr<nsIDOMBlob> blob = aBlob;
     return NS_ERROR_DOM_SECURITY_ERR;
--- a/dom/media/MediaRecorder.h
+++ b/dom/media/MediaRecorder.h
@@ -17,16 +17,17 @@
 namespace mozilla {
 
 class AudioNodeStream;
 class DOMMediaStream;
 class ErrorResult;
 class MediaInputPort;
 struct MediaRecorderOptions;
 class MediaStream;
+class GlobalObject;
 
 namespace dom {
 
 class AudioNode;
 
 /**
  * Implementation of https://dvcs.w3.org/hg/dap/raw-file/default/media-stream-capture/MediaRecorder.html
  * The MediaRecorder accepts a mediaStream as input source passed from UA. When recorder starts,
@@ -73,16 +74,19 @@ public:
   void RequestData(ErrorResult& aResult);
   // Return the The DOMMediaStream passed from UA.
   DOMMediaStream* Stream() const { return mDOMStream; }
   // The current state of the MediaRecorder object.
   RecordingState State() const { return mState; }
   // Return the current encoding MIME type selected by the MediaEncoder.
   void GetMimeType(nsString &aMimeType);
 
+  static bool IsTypeSupported(GlobalObject& aGlobal, const nsAString& aType);
+  static bool IsTypeSupported(const nsAString& aType);
+
   // Construct a recorder with a DOM media stream object as its source.
   static already_AddRefed<MediaRecorder>
   Constructor(const GlobalObject& aGlobal,
               DOMMediaStream& aStream,
               const MediaRecorderOptions& aInitDict,
               ErrorResult& aRv);
   // Construct a recorder with a Web Audio destination node as its source.
   static already_AddRefed<MediaRecorder>
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -727,16 +727,22 @@ tags=msg
 [test_mediarecorder_record_stopms.html]
 tags=msg
 [test_mediarecorder_record_timeslice.html]
 tags=msg capturestream
 [test_mediarecorder_reload_crash.html]
 tags=msg capturestream
 [test_mediarecorder_unsupported_src.html]
 tags=msg
+[test_mediarecorder_webm_support.html]
+skip-if = os == 'android' || arch == 'arm' || arch == 'arm64'
+tags=msg
+[test_mediarecorder_mp4_support.html]
+skip-if = toolkit != 'gonk' || android_version < '17' # Android/Gonk before SDK version 17 does not have the OMX Encoder API.
+tags=msg
 [test_mediarecorder_record_getdata_afterstart.html]
 tags=msg capturestream
 [test_mediatrack_consuming_mediaresource.html]
 [test_mediatrack_consuming_mediastream.html]
 tags=msg
 [test_mediatrack_events.html]
 skip-if = toolkit == 'gonk' && debug # bug 1065924
 [test_mediatrack_parsing_ogg.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_mp4_support.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Media Recording - test mp4 MIME support</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ok(MediaRecorder.isTypeSupported('video/mp4'), 'Should support video/mp4');
+
+</script>
+</head>
+</html>
--- a/dom/media/test/test_mediarecorder_unsupported_src.html
+++ b/dom/media/test/test_mediarecorder_unsupported_src.html
@@ -5,17 +5,25 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=957439">Mozilla Bug 957439</a>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
+
 function startTest() {
+  // also do general checks on mimetype support for audio-only
+  ok(MediaRecorder.isTypeSupported("audio/ogg"), 'Should support audio/ogg');
+  ok(MediaRecorder.isTypeSupported('audio/ogg; codecs="opus"'), 'Should support audio/ogg+opus');
+  ok(!MediaRecorder.isTypeSupported('audio/ogg; codecs="foobar"'), 'Should not support audio/ogg + unknown_codec');
+  ok(!MediaRecorder.isTypeSupported("video/webm"), 'Should not support video/webm');
+  ok(!MediaRecorder.isTypeSupported("video/mp4"), 'Should not support video/mp4');
+
   navigator.mozGetUserMedia({audio: false, video: true, fake: true},
     function(stream) {
 
       // Expected callback sequence should be:
       // 1. onerror (from start)
       // 2. onerror (from pause)
       // 3. ondataavailable
       // 4. onstop
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_mediarecorder_webm_support.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Media Recording - test WebM MIME support</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ok(MediaRecorder.isTypeSupported('video/webm'), 'Should support video/webm');
+ok(MediaRecorder.isTypeSupported('video/webm; codecs="vp8, vorbis"'), 'Should support video/webm + vp8/vorbis');
+ok(!MediaRecorder.isTypeSupported('video/webm; codecs="vp9, vorbis"'), 'Should not support video/webm + vp9/vorbis');
+</script>
+</head>
+</html>
--- a/dom/webidl/MediaRecorder.webidl
+++ b/dom/webidl/MediaRecorder.webidl
@@ -42,16 +42,18 @@ interface MediaRecorder : EventTarget {
   [Throws]
   void pause();
 
   [Throws]
   void resume();
 
   [Throws]
   void requestData();
+
+  static boolean isTypeSupported(DOMString type);
 };
 
 dictionary MediaRecorderOptions {
   DOMString mimeType = ""; // Default encoding mimeType.
   unsigned long audioBitsPerSecond;
   unsigned long videoBitsPerSecond;
   unsigned long bitsPerSecond;
 };