Bug 476731 - Media element should fire error event when src is a 404; r=chris.double sr=roc
authorChris Pearce <chris@pearce.org.nz>
Sun, 15 Feb 2009 17:26:32 +0100
changeset 23455 9a4041b5740eb3aeba99bc500a2290c9442854a9
parent 23454 5201bf6ecd2779303d21f1a88bffd370670eb6ff
child 23456 6f6d5aace10ed652b38b96639c924f8825c17fa6
push id778
push userrocallahan@mozilla.com
push dateThu, 26 Feb 2009 09:56:42 +0000
reviewerschris.double, roc
bugs476731
milestone1.9.1b3pre
Bug 476731 - Media element should fire error event when src is a 404; r=chris.double sr=roc
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLMediaElement.cpp
content/media/video/test/Makefile.in
content/media/video/test/file_access_controls.html
content/media/video/test/test_autoplay.html
content/media/video/test/test_bug463162.xhtml
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/media/video/test/test_constants.html
content/media/video/test/test_controls.html
content/media/video/test/test_currentTime.html
content/media/video/test/test_decoder_disable.html
content/media/video/test/test_media_selection.html
content/media/video/test/test_networkState.html
content/media/video/test/test_paused.html
content/media/video/test/test_play.html
content/media/video/test/test_readyState.html
content/media/video/test/test_seek2.html
content/media/video/test/test_volume.html
dom/public/idl/html/nsIDOMHTMLMediaError.idl
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -208,16 +208,22 @@ protected:
    * false if emptied.
    */
   PRBool AbortExistingLoads();
   /**
    * Create a URI for the given aURISpec string.
    */
   nsresult NewURIFromString(const nsAutoString& aURISpec, nsIURI** aURI);
 
+  /**
+   * Does step 12 of the media load() algorithm, sends error/emptied events to
+   * to the media element, and reset network/begun state.
+   */
+  void NoSupportedMediaError();
+
   nsRefPtr<nsMediaDecoder> mDecoder;
 
   nsCOMPtr<nsIChannel> mChannel;
 
   // Error attribute
   nsCOMPtr<nsIDOMHTMLMediaError> mError;
 
   // Media loading flags. See: 
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -120,28 +120,33 @@ private:
   nsRefPtr<nsHTMLMediaElement> mElement;
   nsCOMPtr<nsIStreamListener> mNextListener;
 };
 
 NS_IMPL_ISUPPORTS2(nsHTMLMediaElement::nsMediaLoadListener, nsIRequestObserver, nsIStreamListener)
 
 NS_IMETHODIMP nsHTMLMediaElement::nsMediaLoadListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
 {
-  nsresult rv;
+  nsresult rv = NS_OK;
 
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
   if (channel &&
       mElement &&
-      NS_SUCCEEDED(mElement->InitializeDecoderForChannel(channel, getter_AddRefs(mNextListener))) &&
+      NS_SUCCEEDED(rv = mElement->InitializeDecoderForChannel(channel, getter_AddRefs(mNextListener))) &&
       mNextListener) {
     rv = mNextListener->OnStartRequest(aRequest, aContext);
   } else {
-    // If InitializeDecoderForChannel did not return a listener, we abort
-    // the connection since we aren't interested in keeping the channel
-    // alive ourselves.
+    // If InitializeDecoderForChannel() returned an error, fire a network
+    // error.
+    if (NS_FAILED(rv) && !mNextListener && mElement) {
+      mElement->NetworkError();
+    }
+    // If InitializeDecoderForChannel did not return a listener (but may
+    // have otherwise succeeded), we abort the connection since we aren't
+    // interested in keeping the channel alive ourselves.
     rv = NS_BINDING_ABORTED;
   }
 
   // The element is only needed until we've had a chance to call
   // InitializeDecoderForChannel.
   mElement = nsnull;
 
   return rv;
@@ -238,26 +243,41 @@ PRBool nsHTMLMediaElement::AbortExisting
     // TODO: The current playback position must be set to 0.
     // TODO: The currentLoop DOM attribute must be set to 0.
     DispatchSimpleEvent(NS_LITERAL_STRING("emptied"));
   }
 
   return PR_FALSE;
 }
 
+void nsHTMLMediaElement::NoSupportedMediaError()
+{
+  mError = new nsHTMLMediaError(nsIDOMHTMLMediaError::MEDIA_ERR_NONE_SUPPORTED);
+  mBegun = PR_FALSE;
+  DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
+  mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
+  DispatchAsyncSimpleEvent(NS_LITERAL_STRING("emptied"));
+}
+
 /* void load (); */
 NS_IMETHODIMP nsHTMLMediaElement::Load()
 {
   if (AbortExistingLoads())
     return NS_OK;
 
+  mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
+  mBegun = PR_TRUE;
+  DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
+
   nsCOMPtr<nsIURI> uri;
   nsresult rv = PickMediaElement(getter_AddRefs(uri));
-  if (NS_FAILED(rv))
+  if (NS_FAILED(rv)) {
+    NoSupportedMediaError();
     return rv;
+  }
 
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannel = nsnull;
   }
 
   PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
   rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_MEDIA,
@@ -265,49 +285,57 @@ NS_IMETHODIMP nsHTMLMediaElement::Load()
                                  NodePrincipal(),
                                  this,
                                  EmptyCString(), // mime type
                                  nsnull, // extra
                                  &shouldLoad,
                                  nsContentUtils::GetContentPolicy(),
                                  nsContentUtils::GetSecurityManager());
   if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
+    NoSupportedMediaError();
     return NS_ERROR_CONTENT_BLOCKED;
   }
 
   rv = NS_NewChannel(getter_AddRefs(mChannel),
                      uri,
                      nsnull,
                      nsnull,
                      nsnull,
                      nsIRequest::LOAD_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
-
+  if (NS_FAILED(rv)) {
+    NetworkError();
+    return rv;
+  }
   // The listener holds a strong reference to us.  This creates a reference
   // cycle which is manually broken in the listener's OnStartRequest method
   // after it is finished with the element.
   nsCOMPtr<nsIStreamListener> loadListener = new nsMediaLoadListener(this);
   NS_ENSURE_TRUE(loadListener, NS_ERROR_OUT_OF_MEMORY);
 
   nsCOMPtr<nsIStreamListener> listener;
   if (ShouldCheckAllowOrigin()) {
     listener = new nsCrossSiteListenerProxy(loadListener,
                                             NodePrincipal(),
                                             mChannel, 
                                             PR_FALSE,
                                             &rv);
     NS_ENSURE_TRUE(listener, NS_ERROR_OUT_OF_MEMORY);
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (NS_FAILED(rv)) {
+      NoSupportedMediaError();
+      return rv;
+    }
   } else {
     rv = nsContentUtils::GetSecurityManager()->
            CheckLoadURIWithPrincipal(NodePrincipal(),
                                      uri,
                                      nsIScriptSecurityManager::STANDARD);
-    NS_ENSURE_SUCCESS(rv, rv);
-
+    if (NS_FAILED(rv)) {
+      NoSupportedMediaError();
+      return rv;
+    }
     listener = loadListener;
   }
 
   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(mChannel);
   if (hc) {
     // Use a byte range request from the start of the resource.
     // This enables us to detect if the stream supports byte range
     // requests, and therefore seeking, early.
@@ -320,25 +348,20 @@ NS_IMETHODIMP nsHTMLMediaElement::Load()
   if (NS_FAILED(rv)) {
     // OnStartRequest is guaranteed to be called if the open succeeds.  If
     // the open failed, the listener's OnStartRequest will never be called,
     // so we need to break the element->channel->listener->element reference
     // cycle here.  The channel holds the only reference to the listener,
     // and is useless now anyway, so drop our reference to it to allow it to
     // be destroyed.
     mChannel = nsnull;
+    NetworkError();
     return rv;
   }
 
-  mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
-
-  mBegun = PR_TRUE;
-
-  DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
-
   return NS_OK;
 }
 
 nsresult nsHTMLMediaElement::LoadWithChannel(nsIChannel *aChannel,
                                              nsIStreamListener **aListener)
 {
   NS_ENSURE_ARG_POINTER(aChannel);
   NS_ENSURE_ARG_POINTER(aListener);
@@ -587,17 +610,19 @@ PRBool nsHTMLMediaElement::ParseAttribut
 nsresult nsHTMLMediaElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                      nsIAtom* aPrefix, const nsAString& aValue,
                                      PRBool aNotify)
 {
   nsresult rv = 
     nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
                                     aNotify);
   if (aNotify && aNameSpaceID == kNameSpaceID_None) {
-    if (aName == nsGkAtoms::src) {
+    if (aName == nsGkAtoms::src &&
+        IsInDoc() &&
+        mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
       Load();
     }
   }
 
   return rv;
 }
 
 nsresult nsHTMLMediaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
@@ -1160,33 +1185,36 @@ nsresult nsHTMLMediaElement::DispatchAsy
 {
   nsCOMPtr<nsIRunnable> event = new nsAsyncEventRunner(aName, this, PR_TRUE);
   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); 
   return NS_OK;                           
 }
 
 nsresult nsHTMLMediaElement::DispatchProgressEvent(const nsAString& aName)
 {
-  if (!mDecoder)
-    return NS_OK;
-
   nsCOMPtr<nsIDOMDocumentEvent> docEvent(do_QueryInterface(GetOwnerDoc()));
   nsCOMPtr<nsIDOMEventTarget> target(do_QueryInterface(static_cast<nsIContent*>(this)));
   NS_ENSURE_TRUE(docEvent && target, NS_ERROR_INVALID_ARG);
 
   nsCOMPtr<nsIDOMEvent> event;
   nsresult rv = docEvent->CreateEvent(NS_LITERAL_STRING("ProgressEvent"), getter_AddRefs(event));
   NS_ENSURE_SUCCESS(rv, rv);
   
   nsCOMPtr<nsIDOMProgressEvent> progressEvent(do_QueryInterface(event));
   NS_ENSURE_TRUE(progressEvent, NS_ERROR_FAILURE);
 
-  nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
+  PRInt64 totalBytes = 0;
+  PRUint64 downloadPosition = 0;
+  if (mDecoder) {
+    nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
+    totalBytes = stats.mTotalBytes;
+    downloadPosition = stats.mDownloadPosition;
+  }
   rv = progressEvent->InitProgressEvent(aName, PR_TRUE, PR_TRUE,
-    stats.mTotalBytes >= 0, stats.mDownloadPosition, stats.mTotalBytes);
+    totalBytes >= 0, downloadPosition, totalBytes);
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRBool dummy;
   return target->DispatchEvent(event, &dummy);  
 }
 
 nsresult nsHTMLMediaElement::DoneAddingChildren(PRBool aHaveNotified)
 {
--- a/content/media/video/test/Makefile.in
+++ b/content/media/video/test/Makefile.in
@@ -69,16 +69,17 @@ ifdef MOZ_OGG
 		test_contentDuration2.html \
 		test_contentDuration3.html \
 		test_contentDuration4.html \
 		test_contentDuration5.html \
 		test_contentDuration6.html \
 		test_duration1.html \
 		test_ended1.html \
 		test_ended2.html \
+		test_error_on_404.html \
 		test_onloadedmetadata.html \
 		test_play.html \
 		test_progress1.html \
 		test_progress3.html \
 		test_standalone.html \
 		test_timeupdate1.html \
 		test_timeupdate2.html \
 		320x240.ogv \
--- a/content/media/video/test/file_access_controls.html
+++ b/content/media/video/test/file_access_controls.html
@@ -1,137 +1,158 @@
 <html>
 <head>
+</head>
+<body onload="setTimeout(load, 0);" onunload="done()">
 <script>
 
 // Page URL: http://example.org/tests/content/media/video/test/file_access_controls.html
 
 var gTests = [
   {
+    // Test 0
     url: "redirect.sjs?http://example.com/tests/content/media/video/test/320x240.ogv",
     result: "error",
     description: "Won't load when redirected to different domain",
   },{
+    // Test 1
     url: "redirect.sjs?http://example.com/tests/content/media/video/test/320x240.allow-origin.ogv",
     result: "loaded",
     description: "Can load when redirected to different domain with allow-origin",
   },{
+    // Test 2
     url: "redirect.sjs?http://test1.example.org/tests/content/media/video/test/320x240.ogv",
     result: "error",
     description: "Won't load when redirected to subdomain",
   },{
+    // Test 3
     url: "redirect.sjs?http://test1.example.org/tests/content/media/video/test/320x240.allow-origin.ogv",
     result: "loaded",
     description: "Can load when redirected to subdomain with allow-origin",
   },{
+    // Test 4
     url: "redirect.sjs?http://example.org/tests/content/media/video/test/320x240.ogv",
     result: "loaded",
     description: "Can load when redirected to same domain",
   },{
+    // Test 5
     url: "http://example.org/tests/content/media/video/test/320x240.ogv",
     result: "loaded",
     description: "Can load from same domain"
   },{
+    // Test 6
     url: "http://example.org:8000/tests/content/media/video/test/320x240.ogv",
     result: "error",
     description: "Won't load from differnet port on same domain"
   },{
+    // Test 7
     url: "http://example.org:8000/tests/content/media/video/test/320x240.allow-origin.ogv",
     result: "loaded",
     description: "Can load from different port on same domain with allow-origin",
   },{
+    // Test 8
     url: "http://example.com/tests/content/media/video/test/320x240.ogv",
     result: "error",
     description: "Won't load cross domain",
   },{
+    // Test 9
     url: "http://example.com/tests/content/media/video/test/320x240.allow-origin.ogv",
     result: "loaded",
     description: "Can load cross domain with allow-origin",
   },{
+    // Test 10
     url: "http://test1.example.org/tests/content/media/video/test/320x240.allow-origin.ogv",
     result: "loaded",
     description: "Can load from subdomain with allow-origin",
   },{
+    // Test 11
     url: "http://test1.example.org/tests/content/media/video/test/320x240.ogv",
     result: "error",
     description: "Won't load from subdomain",
   }
 ];
 
 var gTestNum = 0;
 var gExpectedResult = null;
 var gTestDescription = null;
-var video = null;
+var gVideo = null;
 var gTestedRemoved = false;
 var gOldPref;
 
 function result(code) {
-  dump((gTestNum - 1) + ": " + code + "\n");
+  //dump((gTestNum - 1) + ": " + code + "\n");
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   opener.is(code, gExpectedResult, gTestDescription);
   nextTest();
 }
 
+function createVideo() {
+  var v = document.createElement('video');
+  v.addEventListener('loadeddata', function(){result('loaded');}, false);
+  v.addEventListener('error', function(){result('error');}, false);
+  v.id = 'video';
+  return v;
+}
+
 function load() {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   opener.is(window.location.href,
             "http://example.org/tests/content/media/video/test/file_access_controls.html",
             "We must be on a example.org:80");
-  video = document.getElementById('video');
-  
+ 
   // Ensure access control check pref is on.
   // media.enforce_same_site_origin
   var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefService);
   opener.ok(prefService!=null, "Get pref service");
   var branch = prefService.getBranch("media.");
   opener.ok(branch!=null, "Get media pref branch");
   gOldPref = branch.getBoolPref("enforce_same_site_origin");
   branch.setBoolPref("enforce_same_site_origin", true);
   nextTest();
 }
 
 function nextTest() {
+  //dump("nextTest() called, gTestNum="+gTestNum+" gTestedRemoved="+gTestedRemoved+"\n");
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   if (gTestNum == gTests.length) {
+    //dump("gTestNum == gTests.length\n");
     if (!gTestedRemoved) {
       // Repeat all tests with element removed from doc, should get same result.
-      video.parentNode.removeChild(video);
+      gVideo.parentNode.removeChild(gVideo);
       gTestedRemoved = true;
       gTestNum = 0;
     } else {
+      //dump("Exiting...\n");
       // We're done, exit the test.
       window.close();
       return;
     }
   }
   gExpectedResult = gTests[gTestNum].result;
   gTestDescription = gTests[gTestNum].description;
-  dump("Starting test " + gTestNum + " at " + gTests[gTestNum].url + "\n");
-  video.src = gTests[gTestNum].url;
-  video.load();
+  //dump("Starting test " + gTestNum + " at " + gTests[gTestNum].url + " expecting:" + gExpectedResult + "\n");
+  
+  gVideo = createVideo();
+  gVideo.src = gTests[gTestNum].url;
+  if (!gTestedRemoved) {
+    document.body.appendChild(gVideo); 
+    // Will cause load() to be invoked.
+  } else {
+    gVideo.load();
+  }
   gTestNum++;
 }
 
 function done() {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
   // Undo change to access control check pref.
   var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefService);
   var branch = prefService.getBranch("media.");
   branch.setBoolPref("enforce_same_site_origin", gOldPref);
   opener.done();
 }
 
 </script>
-
-
-</head>
-<body onload="load();" onunload="done()">
-
-  <!-- Change onloadedfirstframe to onloadeddata after bug 462570 lands -->
-  <video id="video"
-         onloadeddata="result('loaded');" 
-         onerror="result('error');">
-  </video>
 </body>
 </html>
 
--- a/content/media/video/test/test_autoplay.html
+++ b/content/media/video/test/test_autoplay.html
@@ -2,18 +2,18 @@
 <html>
 <head>
   <title>Media test: autoplay attribute</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id='v1'></video><audio id='a1'></audio>
-<video id='v2' autoplay></video><audio id='a2' autoplay></audio>
+<video id='v1' onerror="event.stopPropagation();"></video><audio id='a1' onerror="event.stopPropagation();"></audio>
+<video id='v2' onerror="event.stopPropagation();" autoplay></video><audio id='a2' onerror="event.stopPropagation();"autoplay></audio>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var v1 = document.getElementById('v1');
 var a1 = document.getElementById('a1');
 var v2 = document.getElementById('v2');
 var a2 = document.getElementById('a2');
 ok(!v1.autoplay, "v1.autoplay should be false by default");
 ok(!a1.autoplay, "v1.autoplay should be false by default");
--- a/content/media/video/test/test_bug463162.xhtml
+++ b/content/media/video/test/test_bug463162.xhtml
@@ -8,20 +8,20 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug 463162</title>
   <script type="text/javascript" src="/MochiKit/packed.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"/>
   <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=463162">Mozilla Bug 463162</a>
-<audio id='a1'><sauce type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
-<audio id='a2'><source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
-<audio id='a3'><html:source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
-<audio id='a4'><svg:source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
+<audio id='a1' onerror="event.stopPropagation();"><sauce type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
+<audio id='a2' onerror="event.stopPropagation();"><source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
+<audio id='a3' onerror="event.stopPropagation();"><html:source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
+<audio id='a4' onerror="event.stopPropagation();"><svg:source type="audio/x-wav" src="r11025_s16_c1.wav"/></audio>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 ok($("a1").networkState == HTMLMediaElement.NETWORK_EMPTY, "audio a1 with bogus child should not be loading");
 ok($("a2").networkState >= HTMLMediaElement.NETWORK_LOADING, "audio a2 with valid child should be loading");
 ok($("a3").networkState >= HTMLMediaElement.NETWORK_LOADING, "audio a3 with valid child should be loading");
 ok($("a4").networkState == HTMLMediaElement.NETWORK_EMPTY, "audio a4 with bogus child should not be loading");
 ]]>
--- a/content/media/video/test/test_can_play_type.html
+++ b/content/media/video/test/test_can_play_type.html
@@ -11,17 +11,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </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>
+<video id="v" onerror="event.stopPropagation();"></video>
 
 <pre id="test">
 <script type="application/javascript">
 
 var v = document.getElementById('v');
 
 function check(type, expected) {
   is(v.canPlayType(type), expected, type);
--- a/content/media/video/test/test_can_play_type_no_ogg.html
+++ b/content/media/video/test/test_can_play_type_no_ogg.html
@@ -11,17 +11,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </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>
+<video id="v" onerror="event.stopPropagation();"></video>
 
 <pre id="test">
 <script src="can_play_type_ogg.js"></script>
 
 check_ogg(document.getElementById('v'), false);
 
 </script>
 </pre>
--- a/content/media/video/test/test_can_play_type_no_wave.html
+++ b/content/media/video/test/test_can_play_type_no_wave.html
@@ -11,17 +11,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </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>
+<video id="v" onerror="event.stopPropagation();"></video>
 
 <pre id="test">
 <script src="can_play_type_wave.js"></script>
 
 check_wave(document.getElementById('v'), false);
 
 </script>
 </pre>
--- a/content/media/video/test/test_can_play_type_ogg.html
+++ b/content/media/video/test/test_can_play_type_ogg.html
@@ -11,17 +11,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </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>
+<video id="v" onerror="event.stopPropagation();"></video>
 
 <pre id="test">
 <script src="can_play_type_ogg.js"></script>
 <script>
 check_ogg(document.getElementById('v'), true);
 </script>
 </pre>
 </body>
--- a/content/media/video/test/test_can_play_type_wave.html
+++ b/content/media/video/test/test_can_play_type_wave.html
@@ -11,17 +11,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </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>
+<video id="v" onerror="event.stopPropagation();"></video>
 
 <pre id="test">
 <script src="can_play_type_wave.js"></script>
 <script>
 check_wave(document.getElementById('v'), true);
 </script>
 </pre>
 </body>
--- a/content/media/video/test/test_constants.html
+++ b/content/media/video/test/test_constants.html
@@ -6,17 +6,17 @@
 -->
 <head>
   <title>Media test: constants</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video><source></video><audio><source></audio>
+<video onerror="event.stopPropagation();"><source></video><audio onerror="event.stopPropagation();"><source></audio>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 is(HTMLElement.NETWORK_EMPTY, undefined);
 is(HTMLElement.NETWORK_IDLE, undefined);
 is(HTMLElement.NETWORK_LOADING, undefined);
 is(HTMLElement.NETWORK_LOADED, undefined);
 is(HTMLElement.HAVE_NOTHING, undefined);
 is(HTMLElement.HAVE_METADATA, undefined);
--- a/content/media/video/test/test_controls.html
+++ b/content/media/video/test/test_controls.html
@@ -2,18 +2,18 @@
 <html>
 <head>
   <title>Media test: controls</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id='v1'></video><audio id='a1'></audio>
-<video id='v2' controls></video><audio id='a2' controls></audio>
+<video id='v1' onerror="event.stopPropagation();"></video><audio id='a1' onerror="event.stopPropagation();"></audio>
+<video id='v2' onerror="event.stopPropagation();" controls></video><audio id='a2' onerror="event.stopPropagation();" controls></audio>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var v1 = document.getElementById('v1');
 var a1 = document.getElementById('a1');
 var v2 = document.getElementById('v2');
 var a2 = document.getElementById('a2');
 ok(!v1.controls, "v1.controls should be false by default");
 ok(!a1.controls, "v1.controls should be false by default");
--- a/content/media/video/test/test_currentTime.html
+++ b/content/media/video/test/test_currentTime.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <title>Media test: currentTime</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id='v1'></video><audio id='a1'></audio>
+<video id='v1' onerror="event.stopPropagation();"></video><audio id='a1' onerror="event.stopPropagation();"></audio>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var v1 = document.getElementById('v1');
 var a1 = document.getElementById('a1');
 is(v1.currentTime, 0.0);
 is(a1.currentTime, 0.0);
 </script>
 </pre>
--- a/content/media/video/test/test_decoder_disable.html
+++ b/content/media/video/test/test_decoder_disable.html
@@ -23,44 +23,58 @@ var prefService = Components.classes["@m
                .getService(Components.interfaces.nsIPrefService);
 var branch = prefService.getBranch("media.");
 var gOldOggPref = branch.getBoolPref("ogg.enabled");
 var gOldWavePref = branch.getBoolPref("wave.enabled");
 
 branch.setBoolPref("ogg.enabled", false);
 branch.setBoolPref("wave.enabled", false);
 
-</script>
+var gLoadError = new Object();
 
-<video id="video1">
-  <source type="video/ogg" src="320x240.ogv"/>
-  <source type="audio/wave" src="r11025_u8_c1.wav"/>
-</video>
+gLoadError['video1'] = 0; 
+gLoadError['video2'] = 0;
+gLoadError['video3'] = 0;
 
-<video id="video2" src="320x240.ogv"></video>
-<video id="video3" src="r11025_u8_c1.wav"></video>
-
-<script>
+var gErrorCount = 0;
 
 SimpleTest.waitForExplicitFinish();
 
-function doTest() {
-  is(document.getElementById('video1').currentSrc, "");
-  is(document.getElementById('video2').currentSrc, "");
-  is(document.getElementById('video3').currentSrc, "");
+function finishTest() {
+  is(document.getElementById('video1').currentSrc, "", 'video1.currentSrc==""');
+  is(document.getElementById('video2').currentSrc, "", 'video2.currentSrc==""');
+  is(document.getElementById('video3').currentSrc, "", 'video3.currentSrc==""');
 
+  is(gLoadError['video1'], 1, "Load error on video1");
+  is(gLoadError['video2'], 1, "Load error on video2");
+  is(gLoadError['video3'], 1, "Load error on video3");
+  
   netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
 
   branch.setBoolPref("ogg.enabled", gOldOggPref);
   branch.setBoolPref("wave.enabled", gOldWavePref);
 
   SimpleTest.finish();
 }
 
-// Using a timeout here sucks, but we currently have no way
-// to be notified that the element couldn't load. At least if this timeout
-// happens "too early", the test will pass instead of failing.
-setTimeout(doTest, 1000);
+function videoError(event, id) {
+  event.stopPropagation();
+  gLoadError[id]++;
+  gErrorCount++;
+  if (gErrorCount == 3) {
+    finishTest();
+  }
+}
 
 </script>
+
+<video id="video1" onerror="videoError(event, 'video1');">
+  <source type="video/ogg" src="320x240.ogv"/>
+  <source type="audio/wave" src="r11025_u8_c1.wav"/>
+</video>
+
+<video id="video2" src="320x240.ogv" onerror="videoError(event, 'video2');"></video>
+<video id="video3" src="r11025_u8_c1.wav" onerror="videoError(event, 'video3');"></video>
+
 </pre>
+
 </body>
 </html>
--- a/content/media/video/test/test_media_selection.html
+++ b/content/media/video/test/test_media_selection.html
@@ -10,16 +10,18 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
 function maketest(expect_load, attach_media, name, type, check_metadata) {
   return function (testNum) {
     var e = document.createElement('video');
 
+    e.addEventListener('error', function(e) { e.stopPropagation();}, false);
+    
     if (expect_load) {
       // this could be loadedmetadata, but needs bug 466410 fixed
       e.addEventListener('loadeddata', function () {
           ok(e.readyState >= HTMLMediaElement.HAVE_METADATA,
              'test ' +  testNum + ' networkState ' + e.networkState + ' expected >= ' + e.LOADED_METADATA);
           is(e.currentSrc.substring(e.currentSrc.length - name.length), name, 'test ' + testNum);
           check_metadata(e);
           e.parentNode.removeChild(e);
--- a/content/media/video/test/test_networkState.html
+++ b/content/media/video/test/test_networkState.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <title>Media test: networkState</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id='v1'></video><audio id='a1'></audio>
+<video id='v1' onerror="event.stopPropagation();"></video><audio id='a1' onerror="event.stopPropagation();"></audio>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var v1 = document.getElementById('v1');
 var a1 = document.getElementById('a1');
 var passed = true;
 
 is(v1.networkState, 0);
 is(a1.networkState, 0);
--- a/content/media/video/test/test_paused.html
+++ b/content/media/video/test/test_paused.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <title>Media test: paused</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id='v1'></video><audio id='a1'></audio>
+<video id='v1' onerror="event.stopPropagation();"></video><audio id='a1' onerror="event.stopPropagation();"></audio>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var v1 = document.getElementById('v1');
 var a1 = document.getElementById('a1');
 ok(v1.paused, "v1.paused must initially be true");
 ok(a1.paused, "a1.paused must initially be true");
 </script>
 </pre>
--- a/content/media/video/test/test_play.html
+++ b/content/media/video/test/test_play.html
@@ -3,17 +3,17 @@
 <head>
   <title>Media test: play() method</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <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">
-<video id="v"></video>
+<video id="v" onerror="event.stopPropagation();"></video>
 <script>
 SimpleTest.waitForExplicitFinish();
 
 var v = document.getElementById("v");
 
 var playEvents = ["play", "canplay", "playing", "canplaythrough"];
 function gotPlayEvent(event) {
   is(event.type, playEvents.shift(), "Check expected event");
--- a/content/media/video/test/test_readyState.html
+++ b/content/media/video/test/test_readyState.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <title>Media test: readyState</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id='v1'></video><audio id='a1'></audio>
+<video id='v1' onerror="event.stopPropagation();"></video><audio id='a1' onerror="event.stopPropagation();"></audio>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var v1 = document.getElementById('v1');
 var a1 = document.getElementById('a1');
 var passed = true;
 
 is(v1.readyState, 0);
 is(a1.readyState, 0);
--- a/content/media/video/test/test_seek2.html
+++ b/content/media/video/test/test_seek2.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <title>Media test: seek test 2</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<video id='v'></video>
+<video id='v' onerror="event.stopPropagation();"></video>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 // http://www.whatwg.org/specs/web-apps/current-work/#dom-media-seek
 // If the media element's readyState is HAVE_NOTHING, then the user agent
 // must raise an INVALID_STATE_ERR exception.
 var v = document.getElementById('v');
 var passed = false;
 
--- a/content/media/video/test/test_volume.html
+++ b/content/media/video/test/test_volume.html
@@ -3,17 +3,17 @@
 <head>
   <title>Media test: volume attribute set</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
-<video id='v1'></video><audio id='a1'></audio>
+<video id='v1' onerror="event.stopPropagation();"></video><audio id='a1' onerror="event.stopPropagation();"></audio>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 function test(element, value, shouldThrow) {
   var threw = false;
   try {
     element.volume = value;
--- a/dom/public/idl/html/nsIDOMHTMLMediaError.idl
+++ b/dom/public/idl/html/nsIDOMHTMLMediaError.idl
@@ -32,25 +32,28 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #include "domstubs.idl"
 
-[scriptable, uuid(9BEA222F-13CF-42AD-A14E-E01F4F66B833)]
+[scriptable, uuid(7bd8c29f-8a76-453f-9373-79f820f2dc01)]
 interface nsIDOMHTMLMediaError : nsISupports
 {
   /* The download of the media resource was aborted by
      the user agent at the user's requet */
   const unsigned short MEDIA_ERR_ABORTED = 1;
 
   /* A network error of some description caused the 
      user agent to stop downloading the media resource */
   const unsigned short MEDIA_ERR_NETWORK = 2;
 
   /* An error of some description occurred while decoding 
      the media resource */
   const unsigned short MEDIA_ERR_DECODE  = 3;
 
+  /* No suitable media resource could be found */
+  const unsigned short MEDIA_ERR_NONE_SUPPORTED = 4;
+
   readonly attribute unsigned short code;
 };