Bug 566245 - HTMLMediaElement.canPlayType support for WebM. r=roc
authorChris Double <chris.double@double.co.nz>
Wed, 09 Jun 2010 11:31:27 +1200
changeset 43339 e196bc1ce3c50425ba7ce9650f3d7e4a2b9ec328
parent 43338 b391708e95527fd56770a343036d46669220df40
child 43340 d2d79b60f068ecde2a833367ed2d9044efb92f92
push idunknown
push userunknown
push dateunknown
reviewersroc
bugs566245
milestone1.9.3a5pre
Bug 566245 - HTMLMediaElement.canPlayType support for WebM. r=roc
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLMediaElement.cpp
content/media/test/Makefile.in
content/media/test/can_play_type_webm.js
content/media/test/test_can_play_type_no_webm.html
content/media/test/test_can_play_type_webm.html
modules/libpref/src/init/all.js
netwerk/mime/nsMimeTypes.h
uriloader/exthandler/nsExternalHelperAppService.cpp
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -56,16 +56,22 @@ typedef PRUint16 nsMediaNetworkState;
 typedef PRUint16 nsMediaReadyState;
 
 class nsHTMLMediaElement : public nsGenericHTMLElement,
                            public nsIObserver
 {
   typedef mozilla::layers::ImageContainer ImageContainer;
 
 public:
+  enum CanPlayStatus {
+    CANPLAY_NO,
+    CANPLAY_MAYBE,
+    CANPLAY_YES
+  };
+
   nsHTMLMediaElement(nsINodeInfo *aNodeInfo, PRBool aFromParser = PR_FALSE);
   virtual ~nsHTMLMediaElement();
 
   /**
    * This is used when the browser is constructing a video element to play
    * a channel that we've already started loading. The src attribute and
    * <source> children are ignored.
    * @param aChannel the channel to use
@@ -221,22 +227,27 @@ public:
 
   // principal of the currently playing stream
   already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
 
   // Update the visual size of the media. Called from the decoder on the
   // main thread when/if the size changes.
   void UpdateMediaSize(nsIntSize size);
 
-  // Returns true if we can handle this MIME type.
-  // If it returns true, then it also returns a null-terminated list
-  // of supported codecs in *aSupportedCodecs. This
-  // list should not be freed, it is static data.
-  static PRBool CanHandleMediaType(const char* aMIMEType,
-                                   const char*** aSupportedCodecs);
+  // Returns the CanPlayStatus indicating if we can handle this
+  // MIME type. The MIME type should not include the codecs parameter.
+  // If it returns anything other than CANPLAY_NO then it also
+  // returns a null-terminated list of supported codecs
+  // in *aSupportedCodecs. This list should not be freed, it is static data.
+  static CanPlayStatus CanHandleMediaType(const char* aMIMEType,
+                                          const char*** aSupportedCodecs);
+
+  // Returns the CanPlayStatus indicating if we can handle the
+  // full MIME type including the optional codecs parameter.
+  static CanPlayStatus GetCanPlay(const nsAString& aType);
 
   // Returns true if we should handle this MIME type when it appears
   // as an <object> or as a toplevel page. If, in practice, our support
   // for the type is more limited than appears in the wild, we should return
   // false here even if CanHandleMediaType would return true.
   static PRBool ShouldHandleMediaType(const char* aMIMEType);
 
   /**
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -88,16 +88,19 @@
 #include "nsIDocShellTreeItem.h"
 
 #ifdef MOZ_OGG
 #include "nsOggDecoder.h"
 #endif
 #ifdef MOZ_WAVE
 #include "nsWaveDecoder.h"
 #endif
+#ifdef MOZ_WEBM
+#include "nsWebMDecoder.h"
+#endif
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gMediaElementLog;
 static PRLogModuleInfo* gMediaElementEventsLog;
 #define LOG(type, msg) PR_LOG(gMediaElementLog, type, msg)
 #define LOG_EVENT(type, msg) PR_LOG(gMediaElementEventsLog, type, msg)
 #else
 #define LOG(type, msg)
@@ -1242,42 +1245,83 @@ static PRBool IsWaveType(const nsACStrin
   for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gWaveTypes); ++i) {
     if (aType.EqualsASCII(gWaveTypes[i]))
       return PR_TRUE;
   }
   return PR_FALSE;
 }
 #endif
 
+#ifdef MOZ_WEBM
+static const char gWebMTypes[][17] = {
+  "video/webm",
+  "audio/webm"
+};
+
+static const char* gWebMCodecs[] = {
+  "vp8",
+  "vp8.0",
+  "vorbis",
+  nsnull
+};
+
+static PRBool IsWebMEnabled()
+{
+  return nsContentUtils::GetBoolPref("media.webm.enabled");
+}
+
+static PRBool IsWebMType(const nsACString& aType)
+{
+  if (!IsWebMEnabled())
+    return PR_FALSE;
+  for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gWebMTypes); ++i) {
+    if (aType.EqualsASCII(gWebMTypes[i]))
+      return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+#endif
+
 /* static */
-PRBool nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType,
-                                              const char*** aCodecList)
+nsHTMLMediaElement::CanPlayStatus 
+nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType,
+                                       const char*** aCodecList)
 {
 #ifdef MOZ_OGG
   if (IsOggType(nsDependentCString(aMIMEType))) {
     *aCodecList = gOggCodecs;
-    return PR_TRUE;
+    return CANPLAY_MAYBE;
   }
 #endif
 #ifdef MOZ_WAVE
   if (IsWaveType(nsDependentCString(aMIMEType))) {
     *aCodecList = gWaveCodecs;
-    return PR_TRUE;
+    return CANPLAY_MAYBE;
   }
 #endif
-  return PR_FALSE;
+#ifdef MOZ_WEBM
+  if (IsWebMType(nsDependentCString(aMIMEType))) {
+    *aCodecList = gWebMCodecs;
+    return CANPLAY_YES;
+  }
+#endif
+  return CANPLAY_NO;
 }
 
 /* static */
 PRBool nsHTMLMediaElement::ShouldHandleMediaType(const char* aMIMEType)
 {
 #ifdef MOZ_OGG
   if (IsOggType(nsDependentCString(aMIMEType)))
     return PR_TRUE;
 #endif
+#ifdef MOZ_WEBM
+  if (IsWebMType(nsDependentCString(aMIMEType)))
+    return PR_TRUE;
+#endif
   // We should not return true for Wave types, since there are some
   // Wave codecs actually in use in the wild that we don't support, and
   // we should allow those to be handled by plugins or helper apps.
   // Furthermore people can play Wave files on most platforms by other
   // means.
   return PR_FALSE;
 }
 
@@ -1286,41 +1330,39 @@ CodecListContains(const char** aCodecs, 
 {
   for (PRInt32 i = 0; aCodecs[i]; ++i) {
     if (aCodec.EqualsASCII(aCodecs[i]))
       return PR_TRUE;
   }
   return PR_FALSE;
 }
 
-enum CanPlayStatus {
-  CANPLAY_NO,
-  CANPLAY_MAYBE,
-  CANPLAY_YES
-};
-
-static CanPlayStatus GetCanPlay(const nsAString& aType)
+/* static */
+nsHTMLMediaElement::CanPlayStatus
+nsHTMLMediaElement::GetCanPlay(const nsAString& aType)
 {
   nsContentTypeParser parser(aType);
   nsAutoString mimeType;
   nsresult rv = parser.GetType(mimeType);
   if (NS_FAILED(rv))
     return CANPLAY_NO;
 
   NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeType);
   const char** supportedCodecs;
-  if (!nsHTMLMediaElement::CanHandleMediaType(mimeTypeUTF8.get(),
-                                              &supportedCodecs))
+  CanPlayStatus status = CanHandleMediaType(mimeTypeUTF8.get(),
+                                            &supportedCodecs);
+  if (status == CANPLAY_NO)
     return CANPLAY_NO;
 
   nsAutoString codecs;
   rv = parser.GetParameter("codecs", codecs);
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
     // Parameter not found or whatever
-    return CANPLAY_MAYBE;
+    return status;
+  }
 
   CanPlayStatus result = CANPLAY_YES;
   // See http://www.rfc-editor.org/rfc/rfc4281.txt for the description
   // of the 'codecs' parameter
   nsCharSeparatedTokenizer tokenizer(codecs, ',');
   PRBool expectMoreTokens = PR_FALSE;
   while (tokenizer.hasMoreTokens()) {
     const nsSubstring& token = tokenizer.nextToken();
@@ -1360,16 +1402,25 @@ void nsHTMLMediaElement::InitMediaTypes(
     if (IsOggEnabled()) {
       for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gOggTypes); i++) {
         catMan->AddCategoryEntry("Gecko-Content-Viewers", gOggTypes[i],
                                  "@mozilla.org/content/document-loader-factory;1",
                                  PR_FALSE, PR_TRUE, nsnull);
       }
     }
 #endif
+#ifdef MOZ_WEBM
+    if (IsWebMEnabled()) {
+      for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gWebMTypes); i++) {
+        catMan->AddCategoryEntry("Gecko-Content-Viewers", gWebMTypes[i],
+                                 "@mozilla.org/content/document-loader-factory;1",
+                                 PR_FALSE, PR_TRUE, nsnull);
+      }
+    }
+#endif
   }
 }
 
 /* static */
 void nsHTMLMediaElement::ShutdownMediaTypes()
 {
   nsresult rv;
   nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
@@ -1379,16 +1430,21 @@ void nsHTMLMediaElement::ShutdownMediaTy
       catMan->DeleteCategoryEntry("Gecko-Content-Viewers", gOggTypes[i], PR_FALSE);
     }
 #endif
 #ifdef MOZ_WAVE
     for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gWaveTypes); i++) {
       catMan->DeleteCategoryEntry("Gecko-Content-Viewers", gWaveTypes[i], PR_FALSE);
     }
 #endif
+#ifdef MOZ_WEBM
+    for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gWebMTypes); i++) {
+      catMan->DeleteCategoryEntry("Gecko-Content-Viewers", gWebMTypes[i], PR_FALSE);
+    }
+#endif
   }
 }
 
 already_AddRefed<nsMediaDecoder>
 nsHTMLMediaElement::CreateDecoder(const nsACString& aType)
 {
 #ifdef MOZ_OGG
   if (IsOggType(aType)) {
@@ -1401,16 +1457,24 @@ nsHTMLMediaElement::CreateDecoder(const 
 #ifdef MOZ_WAVE
   if (IsWaveType(aType)) {
     nsRefPtr<nsWaveDecoder> decoder = new nsWaveDecoder();
     if (decoder && decoder->Init(this)) {
       return decoder.forget().get();
     }
   }
 #endif
+#ifdef MOZ_WEBM
+  if (IsWebMType(aType)) {
+    nsRefPtr<nsWebMDecoder> decoder = new nsWebMDecoder();
+    if (decoder && decoder->Init(this)) {
+      return decoder.forget().get();
+    }
+  }
+#endif
   return nsnull;
 }
 
 nsresult nsHTMLMediaElement::InitializeDecoderAsClone(nsMediaDecoder* aOriginal)
 {
   nsMediaStream* originalStream = aOriginal->GetCurrentStream();
   if (!originalStream)
     return NS_ERROR_FAILURE;
--- a/content/media/test/Makefile.in
+++ b/content/media/test/Makefile.in
@@ -65,16 +65,17 @@ include $(topsrcdir)/config/rules.mk
 # To test for a specific bug in handling a specific resource type,
 # make the test first check canPlayType for the type, and if it's not
 # supported, just do ok(true, "Type not supported") and stop the test.
 
 _TEST_FILES = \
 		allowed.sjs \
 		can_play_type_ogg.js \
 		can_play_type_wave.js \
+		can_play_type_webm.js \
 		cancellable_request.sjs \
 		dynamic_redirect.sjs \
 		file_access_controls.html \
 		manifest.js \
 		reactivate_helper.html \
 		redirect.sjs \
 		seek1.js \
 		seek2.js \
@@ -236,16 +237,26 @@ ifdef MOZ_OGG
 		contentDuration7.sjs \
 		$(NULL)
 else
 _TEST_FILES += \
 		test_can_play_type_no_ogg.html \
 		$(NULL)
 endif
 
+ifdef MOZ_WEBM
+_TEST_FILES += \
+		test_can_play_type_webm.html \
+		$(NULL)
+else
+_TEST_FILES += \
+		test_can_play_type_no_webm.html \
+		$(NULL)
+endif
+
 ifdef MOZ_WAVE
 _TEST_FILES += \
 		test_can_play_type_wave.html \
 		$(NULL)
 else
 _TEST_FILES += \
 		test_can_play_type_no_wave.html \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/content/media/test/can_play_type_webm.js
@@ -0,0 +1,26 @@
+function check_webm(v, enabled) {
+  function check(type, expected) {
+    is(v.canPlayType(type), enabled ? expected : "no", type);
+  }
+
+  // WebM types
+  check("video/webm", "probably");
+  check("audio/webm", "probably");
+
+  // Supported Webm codecs
+  check("audio/webm; codecs=vorbis", "probably");
+  check("video/webm; codecs=vorbis", "probably");
+  check("video/webm; codecs=vorbis,vp8", "probably");
+  check("video/webm; codecs=vorbis,vp8.0", "probably");
+  check("video/webm; codecs=\"vorbis,vp8\"", "probably");
+  check("video/webm; codecs=\"vorbis,vp8.0\"", "probably");
+  check("video/webm; codecs=\"vp8, vorbis\"", "probably");
+  check("video/webm; codecs=\"vp8.0, vorbis\"", "probably");
+  check("video/webm; codecs=vp8", "probably");
+  check("video/webm; codecs=vp8.0", "probably");
+
+  // Unsupported WebM codecs
+  check("video/webm; codecs=xyz", "");
+  check("video/webm; codecs=xyz,vorbis", "");
+  check("video/webm; codecs=vorbis,xyz", "");
+}
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_can_play_type_no_webm.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566245
+-->
+<head>
+  <title>Test for Bug 566245: WebM backend disabled</title>
+  <script type="application/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566245">Mozill
+a Bug 566245</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v" onerror="event.stopPropagation();"></video>
+
+<pre id="test">
+<script src="can_play_type_webm.js"></script>
+
+check_webm(document.getElementById('v'), false);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_can_play_type_webm.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=566245
+-->
+<head>
+  <title>Test for Bug 566245: WebM backend</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=566245">Mozill
+a Bug 566245</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v" onerror="event.stopPropagation();"></video>
+
+<pre id="test">
+<script src="can_play_type_webm.js"></script>
+<script>
+check_webm(document.getElementById('v'), true);
+</script>
+</pre>
+</body>
+</html>
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -156,16 +156,19 @@ pref("media.enforce_same_site_origin", f
 pref("media.cache_size", 51200);
 
 #ifdef MOZ_OGG
 pref("media.ogg.enabled", true);
 #endif
 #ifdef MOZ_WAVE
 pref("media.wave.enabled", true);
 #endif
+#ifdef MOZ_WEBM
+pref("media.webm.enabled", true);
+#endif
 
 // Whether to autostart a media element with an |autoplay| attribute
 pref("media.autoplay.enabled", true);
 
 // 0 = Off, 1 = Full, 2 = Tagged Images Only. 
 // See eCMSMode in gfx/thebes/public/gfxPlatform.h
 pref("gfx.color_management.mode", 2);
 pref("gfx.color_management.display_profile", "");
--- a/netwerk/mime/nsMimeTypes.h
+++ b/netwerk/mime/nsMimeTypes.h
@@ -99,16 +99,17 @@
 #define APPLICATION_XML                     "application/xml"
 #define APPLICATION_XHTML_XML               "application/xhtml+xml"
 #define APPLICATION_MATHML_XML              "application/mathml+xml"
 #define APPLICATION_RDF_XML                 "application/rdf+xml"
 
 #define AUDIO_BASIC                         "audio/basic"
 #define AUDIO_OGG                           "audio/ogg"
 #define AUDIO_WAV                           "audio/x-wav"
+#define AUDIO_WEBM                          "audio/webm"
 
 #define IMAGE_GIF                           "image/gif"
 #define IMAGE_JPG                           "image/jpeg"
 #define IMAGE_PJPG                          "image/pjpeg"
 #define IMAGE_PNG                           "image/png"
 #define IMAGE_PPM                           "image/x-portable-pixmap"
 #define IMAGE_XBM                           "image/x-xbitmap"
 #define IMAGE_XBM2                          "image/x-xbm"
@@ -151,16 +152,17 @@
 #define TEXT_RDF                            "text/rdf"
 #define TEXT_XUL                            "application/vnd.mozilla.xul+xml"
 #define TEXT_ECMASCRIPT                     "text/ecmascript"
 #define TEXT_JAVASCRIPT                     "text/javascript"
 #define TEXT_XSL                            "text/xsl"
 
 #define VIDEO_MPEG                          "video/mpeg"
 #define VIDEO_OGG                           "video/ogg"
+#define VIDEO_WEBM                          "video/webm"
 #define APPLICATION_OGG                     "application/ogg"
 
 /* x-uuencode-apple-single. QuickMail made me do this. */
 #define UUENCODE_APPLE_SINGLE               "x-uuencode-apple-single"
 
 /* The standard MIME message-content-encoding values:
  */
 #define ENCODING_7BIT                       "7bit"
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -535,17 +535,21 @@ static nsExtraMimeTypeEntry extraMimeEnt
   { APPLICATION_RDF, "rdf", "Resource Description Framework" },
   { TEXT_XUL, "xul", "XML-Based User Interface Language" },
   { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language" },
   { TEXT_CSS, "css", "Style Sheet" },
   { VIDEO_OGG, "ogv", "Ogg Video" },
   { VIDEO_OGG, "ogg", "Ogg Video" },
   { APPLICATION_OGG, "ogg", "Ogg Video"},
   { AUDIO_OGG, "oga", "Ogg Audio" },
-  { AUDIO_WAV, "wav", "Waveform Audio" }
+#ifdef MOZ_WEBM
+  { VIDEO_WEBM, "webm", "Web Media Video" },
+  { AUDIO_WEBM, "webm", "Web Media Audio" },
+#endif
+  { AUDIO_WAV, "wav", "Waveform Audio" },
 };
 
 #undef MAC_TYPE
 
 /**
  * File extensions for which decoding should be disabled.
  * NOTE: These MUST be lower-case and ASCII.
  */