Bug 469247. Implement 'canPlayType' API for video/audio elements. r=doublec,r+sr=bzbarsky
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 17 Dec 2008 15:11:07 +1300
changeset 22874 8ffb3d8bd8b8a7d8541c735f57464029e2eb7eac
parent 22873 4178997e692ab8162fcdcb66df0868deb0cd5bc9
child 22875 84cffcb0f3da0ed6cd7578f2ae03ede75db8d4ef
push idunknown
push userunknown
push dateunknown
reviewersdoublec, r
bugs469247
milestone1.9.2a1pre
Bug 469247. Implement 'canPlayType' API for video/audio elements. r=doublec,r+sr=bzbarsky
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
content/base/src/nsScriptLoader.cpp
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLMediaElement.cpp
content/media/video/test/Makefile.in
content/media/video/test/can_play_type_ogg.js
content/media/video/test/can_play_type_wave.js
content/media/video/test/test_can_play_type.html
content/media/video/test/test_can_play_type_no_ogg.html
content/media/video/test/test_can_play_type_no_wave.html
content/media/video/test/test_can_play_type_ogg.html
content/media/video/test/test_can_play_type_wave.html
content/xul/document/src/nsXULContentSink.cpp
dom/public/idl/html/nsIDOMHTMLMediaElement.idl
layout/build/nsContentDLF.cpp
toolkit/toolkit-makefiles.sh
xpcom/ds/nsCommaSeparatedTokenizer.h
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -54,16 +54,17 @@
 #include "nsIClassInfo.h"
 #include "nsIDOM3Node.h"
 #include "nsDataHashtable.h"
 #include "nsIScriptRuntime.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIDOMEvent.h"
 #include "nsTArray.h"
 #include "nsTextFragment.h"
+#include "nsReadableUtils.h"
 
 struct nsNativeKeyEvent; // Don't include nsINativeKeyBindings.h here: it will force strange compilation error!
 
 class nsIDOMScriptObjectFactory;
 class nsIXPConnect;
 class nsINode;
 class nsIContent;
 class nsIDOMNode;
@@ -107,16 +108,17 @@ class nsIDragSession;
 class nsPIDOMWindow;
 class nsPIDOMEventTarget;
 #ifdef MOZ_XTF
 class nsIXTFService;
 #endif
 #ifdef IBMBIDI
 class nsIBidiKeyboard;
 #endif
+class nsIMIMEHeaderParam;
 
 extern const char kLoadAsData[];
 
 enum EventNameType {
   EventNameType_None = 0x0000,
   EventNameType_HTML = 0x0001,
   EventNameType_XUL = 0x0002,
   EventNameType_SVGGraphic = 0x0004, // svg graphic elements
@@ -1691,9 +1693,25 @@ inline NS_HIDDEN_(PRBool) NS_FloatIsFini
     while (cur) {                                                             \
       type_ *next = cur->member_;                                             \
       cur->member_ = nsnull;                                                  \
       delete cur;                                                             \
       cur = next;                                                             \
     }                                                                         \
   }
 
+class nsContentTypeParser {
+public:
+  nsContentTypeParser(const nsAString& aString);
+  ~nsContentTypeParser();
+
+  nsresult GetParameter(const char* aParameterName, nsAString& aResult);
+  nsresult GetType(nsAString& aResult)
+  {
+    return GetParameter(nsnull, aResult);
+  }
+
+private:
+  NS_ConvertUTF16toUTF8 mString;
+  nsIMIMEHeaderParam*   mService;
+};
+
 #endif /* nsContentUtils_h___ */
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -156,16 +156,17 @@ static NS_DEFINE_CID(kXTFServiceCID, NS_
 #include "nsReferencedElement.h"
 #include "nsIUGenCategory.h"
 #include "nsIDragService.h"
 #include "nsIChannelEventSink.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIOfflineCacheUpdate.h"
 #include "nsCPrefetchService.h"
 #include "nsIChromeRegistry.h"
+#include "nsIMIMEHeaderParam.h"
 
 #ifdef IBMBIDI
 #include "nsIBidiKeyboard.h"
 #endif
 #include "nsCycleCollectionParticipant.h"
 
 // for ReportToConsole
 #include "nsIStringBundle.h"
@@ -4517,8 +4518,27 @@ nsSameOriginChecker::OnChannelRedirect(n
 }
 
 NS_IMETHODIMP
 nsSameOriginChecker::GetInterface(const nsIID & aIID, void **aResult)
 {
   return QueryInterface(aIID, aResult);
 }
 
+nsContentTypeParser::nsContentTypeParser(const nsAString& aString)
+  : mString(aString), mService(nsnull)
+{
+  CallGetService("@mozilla.org/network/mime-hdrparam;1", &mService);
+}
+
+nsContentTypeParser::~nsContentTypeParser()
+{
+  NS_IF_RELEASE(mService);
+}
+
+nsresult
+nsContentTypeParser::GetParameter(const char* aParameterName, nsAString& aResult)
+{
+  NS_ENSURE_TRUE(mService, NS_ERROR_FAILURE);
+  return mService->GetParameter(mString, aParameterName,
+                                EmptyCString(), PR_FALSE, nsnull,
+                                aResult);
+}
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -39,17 +39,16 @@
 
 /*
  * A class that handles loading and evaluation of <script> elements.
  */
 
 #include "nsScriptLoader.h"
 #include "nsIDOMCharacterData.h"
 #include "nsParserUtils.h"
-#include "nsIMIMEHeaderParam.h"
 #include "nsICharsetConverterManager.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsIContent.h"
 #include "nsGkAtoms.h"
 #include "nsNetUtil.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptRuntime.h"
@@ -326,26 +325,20 @@ nsScriptLoader::ProcessScriptElement(nsI
   PRUint32 version = 0;
   nsAutoString language, type, src;
   nsresult rv = NS_OK;
 
   // Check the type attribute to determine language and version.
   // If type exists, it trumps the deprecated 'language='
   aElement->GetScriptType(type);
   if (!type.IsEmpty()) {
-    nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParser =
-      do_GetService("@mozilla.org/network/mime-hdrparam;1");
-    NS_ENSURE_TRUE(mimeHdrParser, NS_ERROR_FAILURE);
-
-    NS_ConvertUTF16toUTF8 typeAndParams(type);
+    nsContentTypeParser parser(type);
 
     nsAutoString mimeType;
-    rv = mimeHdrParser->GetParameter(typeAndParams, nsnull,
-                                     EmptyCString(), PR_FALSE, nsnull,
-                                     mimeType);
+    rv = parser.GetType(mimeType);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Javascript keeps the fast path, optimized for most-likely type
     // Table ordered from most to least likely JS MIME types.
     // See bug 62485, feel free to add <script type="..."> survey data to it,
     // or to a new bug once 62485 is closed.
     static const char *jsTypes[] = {
       "text/javascript",
@@ -374,19 +367,17 @@ nsScriptLoader::ProcessScriptElement(nsI
         NS_WARNING("Failed to find a scripting language");
         typeID = nsIProgrammingLanguage::UNKNOWN;
       } else
         typeID = runtime->GetScriptTypeID();
     }
     if (typeID != nsIProgrammingLanguage::UNKNOWN) {
       // Get the version string, and ensure the language supports it.
       nsAutoString versionName;
-      rv = mimeHdrParser->GetParameter(typeAndParams, "version",
-                                       EmptyCString(), PR_FALSE, nsnull,
-                                       versionName);
+      rv = parser.GetParameter("version", versionName);
       if (NS_FAILED(rv)) {
         // no version attribute - version remains 0.
         if (rv != NS_ERROR_INVALID_ARG)
           return rv;
       } else {
         nsCOMPtr<nsIScriptRuntime> runtime;
         rv = NS_GetScriptRuntimeByID(typeID, getter_AddRefs(runtime));
         if (NS_FAILED(rv)) {
@@ -399,20 +390,17 @@ nsScriptLoader::ProcessScriptElement(nsI
           typeID = nsIProgrammingLanguage::UNKNOWN;
         }
       }
     }
 
     // Some js specifics yet to be abstracted.
     if (typeID == nsIProgrammingLanguage::JAVASCRIPT) {
       nsAutoString value;
-
-      rv = mimeHdrParser->GetParameter(typeAndParams, "e4x",
-                                       EmptyCString(), PR_FALSE, nsnull,
-                                       value);
+      rv = parser.GetParameter("e4x", value);
       if (NS_FAILED(rv)) {
         if (rv != NS_ERROR_INVALID_ARG)
           return rv;
       } else {
         if (value.Length() == 1 && value[0] == '1')
           // This means that we need to set JSOPTION_XML in the JS options.
           // We re-use our knowledge of the implementation to reuse
           // JSVERSION_HAS_XML as a safe version flag.
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -154,18 +154,24 @@ public:
   void UpdateMediaSize(nsIntSize size);
 
   // Handle moving into and out of the bfcache by pausing and playing
   // as needed.
   void Freeze();
   void Thaw();
 
   // Returns true if we can handle this MIME type in a <video> or <audio>
-  // element
-  static PRBool CanHandleMediaType(const char* aMIMEType);
+  // element.
+  // If it returns true, then it also returns a null-terminated list
+  // of supported codecs in *aSupportedCodecs, and a null-terminated list
+  // of codecs that *may* be supported in *aMaybeSupportedCodecs. These
+  // lists should not be freed, they area static data.
+  static PRBool CanHandleMediaType(const char* aMIMEType,
+                                   const char*** aSupportedCodecs,
+                                   const char*** aMaybeSupportedCodecs);
 
   /**
    * Initialize data for available media types
    */
   static void InitMediaTypes();
   /**
    * Shutdown data for available media types
    */
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -50,29 +50,31 @@
 #include "nsNodeInfoManager.h"
 #include "plbase64.h"
 #include "nsNetUtil.h"
 #include "prmem.h"
 #include "nsNetUtil.h"
 #include "nsXPCOMStrings.h"
 #include "prlock.h"
 #include "nsThreadUtils.h"
+#include "nsContentUtils.h"
 
 #include "nsIScriptSecurityManager.h"
 #include "nsIXPConnect.h"
 #include "jsapi.h"
 
 #include "nsIRenderingContext.h"
 #include "nsITimer.h"
 
 #include "nsEventDispatcher.h"
 #include "nsIDOMDocumentEvent.h"
 #include "nsIDOMProgressEvent.h"
 #include "nsHTMLMediaError.h"
 #include "nsICategoryManager.h"
+#include "nsCommaSeparatedTokenizer.h"
 
 #ifdef MOZ_OGG
 #include "nsOggDecoder.h"
 #endif
 #ifdef MOZ_WAVE
 #include "nsWaveDecoder.h"
 #endif
 
@@ -587,22 +589,34 @@ void nsHTMLMediaElement::UnbindFromTree(
 {
   if (!mPaused && mNetworkState != nsIDOMHTMLMediaElement::NETWORK_EMPTY)
     Pause();
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 #ifdef MOZ_OGG
+// See http://www.rfc-editor.org/rfc/rfc5334.txt for the definitions
+// of Ogg media types and codec types
 static const char gOggTypes[][16] = {
   "video/ogg",
   "audio/ogg",
   "application/ogg"
 };
 
+static const char* gOggCodecs[] = {
+  "vorbis",
+  "theora",
+  nsnull
+};
+
+static const char* gOggMaybeCodecs[] = {
+  nsnull
+}; 
+
 static PRBool IsOggEnabled()
 {
   return nsContentUtils::GetBoolPref("media.ogg.enabled");
 }
 
 static PRBool IsOggType(const nsACString& aType)
 {
   if (!IsOggEnabled())
@@ -611,23 +625,36 @@ static PRBool IsOggType(const nsACString
     if (aType.EqualsASCII(gOggTypes[i]))
       return PR_TRUE;
   }
   return PR_FALSE;
 }
 #endif
 
 #ifdef MOZ_WAVE
+// See http://www.rfc-editor.org/rfc/rfc2361.txt for the definitions
+// of WAVE media types and codec types. However, the audio/vnd.wave
+// MIME type described there is not used.
 static const char gWaveTypes[][16] = {
   "audio/x-wav",
   "audio/wav",
   "audio/wave",
   "audio/x-pn-wav"
 };
 
+static const char* gWaveCodecs[] = {
+  "1", // Microsoft PCM Format
+  nsnull
+};
+
+static const char* gWaveMaybeCodecs[] = {
+  "0", // Microsoft Unknown Wave Format
+  nsnull
+};
+
 static PRBool IsWaveEnabled()
 {
   return nsContentUtils::GetBoolPref("media.wave.enabled");
 }
 
 static PRBool IsWaveType(const nsACString& aType)
 {
   if (!IsWaveEnabled())
@@ -636,29 +663,109 @@ static PRBool IsWaveType(const nsACStrin
     if (aType.EqualsASCII(gWaveTypes[i]))
       return PR_TRUE;
   }
   return PR_FALSE;
 }
 #endif
 
 /* static */
-PRBool nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType)
+PRBool nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType,
+                                              const char*** aCodecList,
+                                              const char*** aMaybeCodecList)
 {
 #ifdef MOZ_OGG
-  if (IsOggType(nsDependentCString(aMIMEType)))
+  if (IsOggType(nsDependentCString(aMIMEType))) {
+    *aCodecList = gOggCodecs;
+    *aMaybeCodecList = gOggMaybeCodecs;
     return PR_TRUE;
+  }
 #endif
 #ifdef MOZ_WAVE
-  if (IsWaveType(nsDependentCString(aMIMEType)))
+  if (IsWaveType(nsDependentCString(aMIMEType))) {
+    *aCodecList = gWaveCodecs;
+    *aMaybeCodecList = gWaveMaybeCodecs;
     return PR_TRUE;
+  }
 #endif
   return PR_FALSE;
 }
 
+static PRBool
+CodecListContains(const char** aCodecs, const nsAString& aCodec)
+{
+  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)
+{
+  nsContentTypeParser parser(aType);
+  nsAutoString mimeType;
+  nsresult rv = parser.GetType(mimeType);
+  if (NS_FAILED(rv))
+    return CANPLAY_NO;
+
+  NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeType);
+  const char** supportedCodecs;
+  const char** maybeSupportedCodecs;
+  if (!nsHTMLMediaElement::CanHandleMediaType(mimeTypeUTF8.get(),
+          &supportedCodecs, &maybeSupportedCodecs))
+    return CANPLAY_NO;
+
+  nsAutoString codecs;
+  rv = parser.GetParameter("codecs", codecs);
+  if (NS_FAILED(rv))
+    // Parameter not found or whatever
+    return CANPLAY_MAYBE;
+
+  CanPlayStatus result = CANPLAY_YES;
+  // See http://www.rfc-editor.org/rfc/rfc4281.txt for the description
+  // of the 'codecs' parameter
+  nsCommaSeparatedTokenizer tokenizer(codecs);
+  PRBool expectMoreTokens = PR_FALSE;
+  while (tokenizer.hasMoreTokens()) {
+    const nsSubstring& token = tokenizer.nextToken();
+
+    if (CodecListContains(maybeSupportedCodecs, token)) {
+      result = CANPLAY_MAYBE;
+    } else if (!CodecListContains(supportedCodecs, token)) {
+      // Totally unsupported codec
+      return CANPLAY_NO;
+    }
+    expectMoreTokens = tokenizer.lastTokenEndedWithComma();
+  }
+  if (expectMoreTokens) {
+    // Last codec name was empty
+    return CANPLAY_NO;
+  }
+  return result;
+}
+
+NS_IMETHODIMP
+nsHTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult)
+{
+  switch (GetCanPlay(aType)) {
+  case CANPLAY_NO: aResult.AssignLiteral("no"); break;
+  case CANPLAY_YES: aResult.AssignLiteral("probably"); break;
+  default:
+  case CANPLAY_MAYBE: aResult.AssignLiteral("maybe"); break;
+  }
+  return NS_OK;
+}
+
 /* static */
 void nsHTMLMediaElement::InitMediaTypes()
 {
   nsresult rv;
   nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
   if (NS_SUCCEEDED(rv)) {
 #ifdef MOZ_OGG
     if (IsOggEnabled()) {
@@ -791,19 +898,18 @@ nsresult nsHTMLMediaElement::PickMediaEl
     nsCOMPtr<nsIContent> source = do_QueryInterface(child);
     if (source &&
         source->Tag() == nsGkAtoms::source &&
         source->IsNodeOfType(nsINode::eHTML)) {
       nsAutoString type;
       nsAutoString src;
       if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
         if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
-          if (CanHandleMediaType(NS_ConvertUTF16toUTF8(type).get())) {
+          if (GetCanPlay(type) != CANPLAY_NO)
             return NewURIFromString(src, aURI);
-          }
         } else if (i + 1 == count) {
           // The last source element is permitted to omit the type
           // attribute, in which case we need to open the URI and examine
           // the channel's MIME type.
           return NewURIFromString(src, aURI);
         }
       }
     }
--- a/content/media/video/test/Makefile.in
+++ b/content/media/video/test/Makefile.in
@@ -39,31 +39,35 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = content/media/video/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
+		can_play_type_ogg.js \
+		can_play_type_wave.js \
 		test_autoplay.html \
+		test_can_play_type.html \
 		test_constants.html \
 		test_controls.html \
 		test_currentTime.html \
 		test_networkState.html \
 		test_paused.html \
 		test_readyState.html \
 		test_seek2.html \
 		test_volume.html \
 		$(NULL)
 
 ifdef MOZ_OGG
 _TEST_FILES += \
 		test_bug448534.html \
 		test_bug461281.html \
+		test_can_play_type_ogg.html \
 		test_duration1.html \
 		test_ended1.html \
 		test_ended2.html \
 		test_onloadedmetadata.html \
 		test_seek1.html \
 		test_seek3.html \
 		test_seek4.html \
 		test_seek5.html \
@@ -73,21 +77,26 @@ ifdef MOZ_OGG
 		test_standalone.html \
 		test_timeupdate1.html \
 		test_timeupdate2.html \
 		test_timeupdate3.html \
 		320x240.ogg \
 		bug461281.ogg \
 		seek.ogg \
 		$(NULL)
+else
+_TEST_FILES += \
+		test_can_play_type_no_ogg.html \
+		$(NULL)
 endif
 
 ifdef MOZ_WAVE
 _TEST_FILES += \
 		test_bug463162.xhtml \
+		test_can_play_type_wave.html \
 		test_wav_8bit.html \
 		test_wav_ended1.html \
 		test_wav_seek3.html \
 		test_wav_seek4.html \
 		test_wav_seek5.html \
 		test_wav_seek6.html \
 		test_wav_seek7.html \
 		test_wav_seek8.html \
@@ -96,16 +105,20 @@ ifdef MOZ_WAVE
 		test_wav_trailing.html \
 		test_wav_trunc.html \
 		test_wav_trunc_seek.html \
 		r11025_s16_c1.wav \
 		r11025_s16_c1_trailing.wav \
 		r11025_u8_c1.wav \
 		r11025_u8_c1_trunc.wav \
 		$(NULL)
+else
+_TEST_FILES += \
+		test_can_play_type_no_wave.html \
+		$(NULL)
 endif
 
 ifdef MOZ_OGG
 ifdef MOZ_WAVE
 _TEST_FILES += \
 		test_decoder_disable.html \
 		test_media_selection.html \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/content/media/video/test/can_play_type_ogg.js
@@ -0,0 +1,22 @@
+function check_ogg(v, enabled) {
+  function check(type, expected) {
+    is(v.canPlayType(type), enabled ? expected : "no", type);
+  }
+
+  // Ogg types
+  check("video/ogg", "maybe");
+  check("audio/ogg", "maybe");
+  check("application/ogg", "maybe");
+
+  // Supported Ogg codecs
+  check("audio/ogg; codecs=vorbis", "probably");
+  check("video/ogg; codecs=vorbis", "probably");
+  check("video/ogg; codecs=vorbis,theora", "probably");
+  check("video/ogg; codecs=\"vorbis, theora\"", "probably");
+  check("video/ogg; codecs=theora", "probably");
+
+  // Unsupported Ogg codecs
+  check("video/ogg; codecs=xyz", "no");
+  check("video/ogg; codecs=xyz,vorbis", "no");
+  check("video/ogg; codecs=vorbis,xyz", "no");
+}
new file mode 100644
--- /dev/null
+++ b/content/media/video/test/can_play_type_wave.js
@@ -0,0 +1,30 @@
+function check_wave(v, enabled) {
+  function check(type, expected) {
+    is(v.canPlayType(type), enabled ? expected : "no", type);
+  }
+
+  // Wave types
+  check("audio/wave", "maybe");
+  check("audio/wav", "maybe");
+  check("audio/x-wav", "maybe");
+  check("audio/x-pn-wav", "maybe");
+
+  // Supported Wave codecs
+  check("audio/wave; codecs=1", "probably");
+  // "no codecs" should be supported, I guess
+  check("audio/wave; codecs=", "probably");
+  check("audio/wave; codecs=\"\"", "probably");
+
+  // Maybe-supported Wave codecs
+  check("audio/wave; codecs=0", "maybe");
+  check("audio/wave; codecs=\"0, 1\"", "maybe");
+
+  // Unsupported Wave codecs
+  check("audio/wave; codecs=2", "no");
+  check("audio/wave; codecs=xyz,0", "no");
+  check("audio/wave; codecs=0,xyz", "no");
+  check("audio/wave; codecs=\"xyz, 1\"", "no");
+  // empty codec names
+  check("audio/wave; codecs=,", "no");
+  check("audio/wave; codecs=\"0, 1,\"", "no");
+}
new file mode 100644
--- /dev/null
+++ b/content/media/video/test/test_can_play_type.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+  <title>Test for Bug 469247</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=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script type="application/javascript">
+
+var v = document.getElementById('v');
+
+function check(type, expected) {
+  is(v.canPlayType(type), expected, type);
+}
+
+// Invalid types
+check("foo/bar", "no");
+check("", "no");
+check("!!!", "no");
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/video/test/test_can_play_type_no_ogg.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+  <title>Test for Bug 469247: Ogg 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=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script src="can_play_type_ogg.js"></script>
+
+check_ogg(document.getElementById('v'), false);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/video/test/test_can_play_type_no_wave.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+  <title>Test for Bug 469247: WAVE 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=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script src="can_play_type_wave.js"></script>
+
+check_wave(document.getElementById('v'), false);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/video/test/test_can_play_type_ogg.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+  <title>Test for Bug 469247: Ogg backend</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=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script src="can_play_type_ogg.js"></script>
+<script>
+check_ogg(document.getElementById('v'), true);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/video/test/test_can_play_type_wave.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469247
+-->
+<head>
+  <title>Test for Bug 469247: WAVE backend</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=469247">Mozill
+a Bug 469247</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+
+<video id="v"></video>
+
+<pre id="test">
+<script src="can_play_type_wave.js"></script>
+<script>
+check_wave(document.getElementById('v'), true);
+</script>
+</pre>
+</body>
+</html>
--- a/content/xul/document/src/nsXULContentSink.cpp
+++ b/content/xul/document/src/nsXULContentSink.cpp
@@ -69,17 +69,16 @@
 #include "nsIURL.h"
 #include "nsIViewManager.h"
 #include "nsIXULDocument.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsLayoutCID.h"
 #include "nsNetUtil.h"
 #include "nsRDFCID.h"
 #include "nsParserUtils.h"
-#include "nsIMIMEHeaderParam.h"
 #include "nsXPIDLString.h"
 #include "nsReadableUtils.h"
 #include "nsXULElement.h"
 #include "prlog.h"
 #include "prmem.h"
 #include "jscntxt.h"  // for JSVERSION_HAS_XML
 #include "nsCRT.h"
 
@@ -978,26 +977,20 @@ XULContentSinkImpl::OpenScript(const PRU
   // Look for SRC attribute and look for a LANGUAGE attribute
   nsAutoString src;
   while (*aAttributes) {
       const nsDependentString key(aAttributes[0]);
       if (key.EqualsLiteral("src")) {
           src.Assign(aAttributes[1]);
       }
       else if (key.EqualsLiteral("type")) {
-          nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParser =
-              do_GetService("@mozilla.org/network/mime-hdrparam;1");
-          NS_ENSURE_TRUE(mimeHdrParser, NS_ERROR_FAILURE);
-
-          NS_ConvertUTF16toUTF8 typeAndParams(aAttributes[1]);
-
+          nsDependentString str(aAttributes[1]);
+          nsContentTypeParser parser(str);
           nsAutoString mimeType;
-          rv = mimeHdrParser->GetParameter(typeAndParams, nsnull,
-                                           EmptyCString(), PR_FALSE, nsnull,
-                                           mimeType);
+          rv = parser.GetType(mimeType);
           if (NS_FAILED(rv)) {
               if (rv == NS_ERROR_INVALID_ARG) {
                   // Might as well bail out now instead of setting langID to
                   // nsIProgrammingLanguage::UNKNOWN and bailing out later.
                   return NS_OK;
               }
               // We do want the warning here
               NS_ENSURE_SUCCESS(rv, rv);
@@ -1038,19 +1031,17 @@ XULContentSinkImpl::OpenScript(const PRU
                   langID = nsIProgrammingLanguage::UNKNOWN;
               } else
                   langID = runtime->GetScriptTypeID();
           }
 
           if (langID != nsIProgrammingLanguage::UNKNOWN) {
             // Get the version string, and ensure the language supports it.
             nsAutoString versionName;
-            rv = mimeHdrParser->GetParameter(typeAndParams, "version",
-                                             EmptyCString(), PR_FALSE, nsnull,
-                                             versionName);
+            rv = parser.GetParameter("version", versionName);
             if (NS_FAILED(rv)) {
               if (rv != NS_ERROR_INVALID_ARG)
                 return rv;
               // no version specified - version remains the default.
             } else {
               nsCOMPtr<nsIScriptRuntime> runtime;
               rv = NS_GetScriptRuntimeByID(langID, getter_AddRefs(runtime));
               if (NS_FAILED(rv))
@@ -1066,19 +1057,17 @@ XULContentSinkImpl::OpenScript(const PRU
           if (langID == nsIProgrammingLanguage::JAVASCRIPT) {
               // By default scripts in XUL documents have E4X turned on. We use
               // our implementation knowledge to reuse JSVERSION_HAS_XML as a
               // safe version flag. This is still OK if version is
               // JSVERSION_UNKNOWN (-1),
               version |= JSVERSION_HAS_XML;
 
               nsAutoString value;
-              rv = mimeHdrParser->GetParameter(typeAndParams, "e4x",
-                                               EmptyCString(), PR_FALSE, nsnull,
-                                               value);
+              rv = parser.GetParameter("e4x", value);
               if (NS_FAILED(rv)) {
                   if (rv != NS_ERROR_INVALID_ARG)
                       return rv;
               } else {
                   if (value.Length() == 1 && value[0] == '0')
                     version &= ~JSVERSION_HAS_XML;
               }
           }
--- a/dom/public/idl/html/nsIDOMHTMLMediaElement.idl
+++ b/dom/public/idl/html/nsIDOMHTMLMediaElement.idl
@@ -66,16 +66,17 @@ interface nsIDOMHTMLMediaElement : nsIDO
            attribute DOMString src;
   readonly attribute DOMString currentSrc;
   const unsigned short NETWORK_EMPTY = 0;
   const unsigned short NETWORK_IDLE = 1;
   const unsigned short NETWORK_LOADING = 2;
   const unsigned short NETWORK_LOADED = 3;
   readonly attribute unsigned short networkState;
   void load();
+  DOMString canPlayType(in DOMString type);
 
   // ready state
   const unsigned short HAVE_NOTHING = 0;
   const unsigned short HAVE_METADATA = 1;
   const unsigned short HAVE_CURRENT_DATA = 2;
   const unsigned short HAVE_FUTURE_DATA = 3;
   const unsigned short HAVE_ENOUGH_DATA = 4;
   readonly attribute unsigned short readyState;
--- a/layout/build/nsContentDLF.cpp
+++ b/layout/build/nsContentDLF.cpp
@@ -262,17 +262,20 @@ nsContentDLF::CreateInstance(const char*
       return CreateXULDocument(aCommand, 
                                aChannel, aLoadGroup,
                                aContentType, aContainer,
                                aExtraInfo, aDocListener, aDocViewer);
     }
   }
 
 #ifdef MOZ_MEDIA
-  if (nsHTMLMediaElement::CanHandleMediaType(aContentType)) {
+  const char** supportedCodecs;
+  const char** maybeSupportedCodecs;
+  if (nsHTMLMediaElement::CanHandleMediaType(aContentType,
+          &supportedCodecs, &maybeSupportedCodecs)) {
     return CreateDocument(aCommand, 
                           aChannel, aLoadGroup,
                           aContainer, kVideoDocumentCID,
                           aDocListener, aDocViewer);
   }  
 #endif
 
   // Try image types
--- a/toolkit/toolkit-makefiles.sh
+++ b/toolkit/toolkit-makefiles.sh
@@ -1061,16 +1061,17 @@ else
 fi # MOZ_COMPONENTLIB
 
 if [ "$MOZ_MEDIA" ]; then
  add_makefiles "
    content/media/Makefile
    content/media/video/Makefile
    content/media/video/public/Makefile
    content/media/video/src/Makefile
+   content/media/video/test/Makefile
  "
 fi
 
 if [ "$MOZ_OGG" ]; then
  add_makefiles "
    $MAKEFILES_libvorbis
    $MAKEFILES_libtheora
    $MAKEFILES_liboggz
--- a/xpcom/ds/nsCommaSeparatedTokenizer.h
+++ b/xpcom/ds/nsCommaSeparatedTokenizer.h
@@ -75,16 +75,21 @@ public:
     PRBool hasMoreTokens()
     {
         NS_ASSERTION(mIter == mEnd || !isWhitespace(*mIter),
                      "Should be at beginning of token if there is one");
 
         return mIter != mEnd;
     }
 
+    PRBool lastTokenEndedWithComma()
+    {
+        return mLastTokenEndedWithComma;
+    }
+
     /**
      * Returns the next token.
      */
     const nsDependentSubstring nextToken()
     {
         nsSubstring::const_char_iterator end = mIter, begin = mIter;
 
         NS_ASSERTION(mIter == mEnd || !isWhitespace(*mIter),
@@ -96,32 +101,34 @@ public:
               ++mIter;
           }
           end = mIter;
 
           while (mIter != mEnd && isWhitespace(*mIter)) {
               ++mIter;
           }
         }
-        
+        mLastTokenEndedWithComma = mIter != mEnd;
+
         // Skip comma
-        if (mIter != mEnd) {
+        if (mLastTokenEndedWithComma) {
             NS_ASSERTION(*mIter == ',', "Ended loop too soon");
             ++mIter;
 
             while (mIter != mEnd && isWhitespace(*mIter)) {
                 ++mIter;
             }
         }
         
         return Substring(begin, end);
     }
 
 private:
     nsSubstring::const_char_iterator mIter, mEnd;
+    PRPackedBool mLastTokenEndedWithComma;
 
     PRBool isWhitespace(PRUnichar aChar)
     {
         return aChar <= ' ' &&
                (aChar == ' ' || aChar == '\n' ||
                 aChar == '\r'|| aChar == '\t');
     }
 };