Bug 448603. Support direct loading of Ogg audio and video files by creating a synthetic <video> document. r=doublec,sr=bzbarsky
authorRobert O'Callahan <robert@ocallahan.org>
Wed, 29 Oct 2008 22:20:08 -0700
changeset 21079 e8cd2199cf0c8eeadce6217223c191006583b6f3
parent 21078 90e79fb3d8d61de3f4ecf018b39430f860fc919f
child 21080 45f2c96d4a9b04d774443a1b838ae761a42bfb2f
push idunknown
push userunknown
push dateunknown
reviewersdoublec, bzbarsky
bugs448603
milestone1.9.1b2pre
Bug 448603. Support direct loading of Ogg audio and video files by creating a synthetic <video> document. r=doublec,sr=bzbarsky
content/base/public/nsContentCID.h
content/base/public/nsIDocument.h
content/html/content/public/nsHTMLAudioElement.h
content/html/content/public/nsHTMLMediaElement.h
content/html/content/public/nsHTMLVideoElement.h
content/html/content/src/nsHTMLAudioElement.cpp
content/html/content/src/nsHTMLMediaElement.cpp
content/html/content/src/nsHTMLVideoElement.cpp
content/html/document/src/Makefile.in
content/html/document/src/nsImageDocument.cpp
content/html/document/src/nsMediaDocument.h
content/html/document/src/nsVideoDocument.cpp
content/media/video/public/nsChannelReader.h
content/media/video/public/nsMediaDecoder.h
content/media/video/public/nsMediaStream.h
content/media/video/public/nsOggDecoder.h
content/media/video/src/nsChannelReader.cpp
content/media/video/src/nsMediaStream.cpp
content/media/video/src/nsOggDecoder.cpp
content/media/video/test/Makefile.in
content/media/video/test/test_standalone.html
layout/build/Makefile.in
layout/build/nsContentDLF.cpp
layout/build/nsLayoutModule.cpp
layout/build/nsLayoutStatics.cpp
uriloader/exthandler/nsExternalHelperAppService.cpp
--- a/content/base/public/nsContentCID.h
+++ b/content/base/public/nsContentCID.h
@@ -276,16 +276,25 @@
 
 #define NS_SVGDOCUMENT_CID                        \
 { /* b7f44954-1dd1-11b2-8c2e-c2feab4186bc */      \
   0xb7f44954, 0x11d1, 0x11b2,                     \
   {0x8c, 0x2e, 0xc2, 0xfe, 0xab, 0x41, 0x86, 0xbc}}
 
 #endif // MOZ_SVG
 
+#ifdef MOZ_MEDIA
+
+// {d899a152-9412-46b2-b651-2e71c5c2f05f}
+#define NS_VIDEODOCUMENT_CID   \
+{ 0xd899a152, 0x9412, 0x46b2,  \
+  { 0xb6, 0x51, 0x2e, 0x71, 0xc5, 0xc2, 0xf0, 0x5f } }
+
+#endif
+
 #define NS_SYNCLOADDOMSERVICE_CID                   \
  { /* 0e4e7d00-f71a-439f-9178-1a71ff11b55f */       \
   0x0e4e7d00, 0xf71a, 0x439f,                       \
  {0x91, 0x78, 0x1a, 0x71, 0xff, 0x11, 0xb5, 0x5f} }
 #define NS_SYNCLOADDOMSERVICE_CONTRACTID            \
 "@mozilla.org/content/syncload-dom-service;1"
 
 // {f96f5ec9-755b-447e-b1f3-717d1a84bb41}
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -1257,16 +1257,21 @@ NS_NewXMLDocument(nsIDocument** aInstanc
 #ifdef MOZ_SVG
 nsresult
 NS_NewSVGDocument(nsIDocument** aInstancePtrResult);
 #endif
 
 nsresult
 NS_NewImageDocument(nsIDocument** aInstancePtrResult);
 
+#ifdef MOZ_MEDIA
+nsresult
+NS_NewVideoDocument(nsIDocument** aInstancePtrResult);
+#endif
+
 nsresult
 NS_NewDocumentFragment(nsIDOMDocumentFragment** aInstancePtrResult,
                        nsNodeInfoManager *aNodeInfoManager);
 
 // Note: it's the caller's responsibility to create or get aPrincipal as needed
 // -- this method will not attempt to get a principal based on aDocumentURI.
 // Also, both aDocumentURI and aBaseURI must not be null.
 nsresult
--- a/content/html/content/public/nsHTMLAudioElement.h
+++ b/content/html/content/public/nsHTMLAudioElement.h
@@ -67,11 +67,9 @@ public:
   NS_DECL_NSIDOMHTMLAUDIOELEMENT
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
   virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                               PRBool aNullParent = PR_TRUE);
-protected:
-  virtual nsresult InitializeDecoder(nsAString& aChosenMediaResource);
 };
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -33,29 +33,40 @@
  * 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 "nsIDOMHTMLMediaElement.h"
 #include "nsGenericHTMLElement.h"
 #include "nsMediaDecoder.h"
+#include "nsIChannel.h"
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 typedef PRUint16 nsMediaNetworkState;
 typedef PRUint16 nsMediaReadyState;
 
 class nsHTMLMediaElement : public nsGenericHTMLElement
 {
 public:
   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
+   * @param aListener returns a stream listener that should receive
+   * notifications for the stream
+   */ 
+  nsresult LoadWithChannel(nsIChannel *aChannel, nsIStreamListener **aListener);
+
   // nsIDOMHTMLMediaElement
   NS_DECL_NSIDOMHTMLMEDIAELEMENT
 
   virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult);
   // SetAttr override.  C++ is stupid, so have to override both
@@ -142,19 +153,50 @@ public:
   // main thread when/if the size changes.
   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);
+
+  /**
+   * Initialize data for available media types
+   */
+  static void InitMediaTypes();
+  /**
+   * Shutdown data for available media types
+   */
+  static void ShutdownMediaTypes();
+
 protected:
-  nsresult PickMediaElement(nsAString& aChosenMediaResource);
-  virtual nsresult InitializeDecoder(nsAString& aChosenMediaResource);
+  /**
+   * Figure out which resource to load (either the 'src' attribute or
+   * a <source> child) and create the decoder for it.
+   */
+  nsresult PickMediaElement();
+  /**
+   * Create a decoder for the given aMIMEType. Returns false if we
+   * were unable to create the decoder.
+   */
+  PRBool CreateDecoder(const nsACString& aMIMEType);
+  /**
+   * Initialize a decoder to load the given URI.
+   */
+  nsresult InitializeDecoder(const nsAString& aURISpec);
+  /**
+   * Initialize a decoder to load the given channel. The decoder's stream
+   * listener is returned via aListener.
+   */
+  nsresult InitializeDecoderForChannel(nsIChannel *aChannel,
+                                       nsIStreamListener **aListener);
 
   nsRefPtr<nsMediaDecoder> mDecoder;
 
   // Error attribute
   nsCOMPtr<nsIDOMHTMLMediaError> mError;
 
   // Media loading flags. See: 
   //   http://www.whatwg.org/specs/web-apps/current-work/#video)
--- a/content/html/content/public/nsHTMLVideoElement.h
+++ b/content/html/content/public/nsHTMLVideoElement.h
@@ -68,13 +68,9 @@ public:
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
   virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                               PRBool aNullParent = PR_TRUE);
 
   // Returns the current video frame width and height.
   // If there is no video frame, returns the given default size.
   nsIntSize GetVideoSize(nsIntSize defaultSize);
-
-protected:
-  virtual nsresult InitializeDecoder(nsAString& aChosenMediaResource);
-
 };
--- a/content/html/content/src/nsHTMLAudioElement.cpp
+++ b/content/html/content/src/nsHTMLAudioElement.cpp
@@ -105,16 +105,8 @@ nsresult nsHTMLAudioElement::BindToTree(
 void nsHTMLAudioElement::UnbindFromTree(PRBool aDeep,
                                         PRBool aNullParent)
 {
   if (mDecoder) 
     mDecoder->ElementUnavailable();
 
   nsHTMLMediaElement::UnbindFromTree(aDeep, aNullParent);
 }
-
-nsresult nsHTMLAudioElement::InitializeDecoder(nsAString& aChosenMediaResource)
-{
-  if (mDecoder) 
-    mDecoder->ElementAvailable(this);
-
-  return nsHTMLMediaElement::InitializeDecoder(aChosenMediaResource);
-}
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -62,16 +62,17 @@
 
 #include "nsIRenderingContext.h"
 #include "nsITimer.h"
 
 #include "nsEventDispatcher.h"
 #include "nsIDOMDocumentEvent.h"
 #include "nsIDOMProgressEvent.h"
 #include "nsHTMLMediaError.h"
+#include "nsICategoryManager.h"
 
 #ifdef MOZ_OGG
 #include "nsOggDecoder.h"
 #endif
 
 class nsAsyncEventRunner : public nsRunnable
 {
 private:
@@ -215,16 +216,29 @@ NS_IMETHODIMP nsHTMLMediaElement::GetTot
 {
   *aTotalBytes = mDecoder ? PRUint32(mDecoder->GetTotalBytes()) : 0;
   return NS_OK;
 }
 
 /* void load (); */
 NS_IMETHODIMP nsHTMLMediaElement::Load()
 {
+  return LoadWithChannel(nsnull, nsnull);
+}
+
+nsresult nsHTMLMediaElement::LoadWithChannel(nsIChannel *aChannel,
+                                             nsIStreamListener **aListener)
+{
+  NS_ASSERTION((aChannel == nsnull) == (aListener == nsnull),
+               "channel and listener should both be null or both non-null");
+
+  if (aListener) {
+    *aListener = nsnull;
+  }
+
   if (mBegun) {
     mBegun = PR_FALSE;
     
     mError = new nsHTMLMediaError(nsHTMLMediaError::MEDIA_ERR_ABORTED);
     DispatchProgressEvent(NS_LITERAL_STRING("abort"));
     return NS_OK;
   }
 
@@ -240,24 +254,22 @@ NS_IMETHODIMP nsHTMLMediaElement::Load()
     mNetworkState = nsIDOMHTMLMediaElement::EMPTY;
     ChangeReadyState(nsIDOMHTMLMediaElement::DATA_UNAVAILABLE);
     mPaused = PR_TRUE;
     // 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"));
   }
 
-  nsAutoString chosenMediaResource;
-  nsresult rv = PickMediaElement(chosenMediaResource);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  mNetworkState = nsIDOMHTMLMediaElement::LOADING;
-  
-  // This causes the currentSrc attribute to become valid
-  rv = InitializeDecoder(chosenMediaResource);
+  nsresult rv;
+  if (aChannel) {
+    rv = InitializeDecoderForChannel(aChannel, aListener);
+  } else {
+    rv = PickMediaElement();
+  }
   NS_ENSURE_SUCCESS(rv, rv);
 
   mBegun = PR_TRUE;
   mEnded = PR_FALSE;
 
   DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
 
   return NS_OK;
@@ -559,114 +571,188 @@ nsresult nsHTMLMediaElement::BindToTree(
                                         PRBool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, 
                                                  aParent, 
                                                  aBindingParent, 
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (mIsDoneAddingChildren && mNetworkState == nsIDOMHTMLMediaElement::EMPTY) {
+  if (mIsDoneAddingChildren &&
+      mNetworkState == nsIDOMHTMLMediaElement::EMPTY) {
     Load();
   }
 
   return rv;
 }
 
 void nsHTMLMediaElement::UnbindFromTree(PRBool aDeep,
                                         PRBool aNullParent)
 {
   if (!mPaused && mNetworkState != nsIDOMHTMLMediaElement::EMPTY)
     Pause();
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
+#ifdef MOZ_OGG
+static const char gOggTypes[][16] = {
+  "video/ogg",
+  "audio/ogg",
+  "application/ogg"
+};
 
-nsresult nsHTMLMediaElement::PickMediaElement(nsAString& aChosenMediaResource)
+static PRBool IsOggType(const nsACString& aType)
+{
+  for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gOggTypes); ++i) {
+    if (aType.EqualsASCII(gOggTypes[i]))
+      return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+#endif
+
+/* static */
+PRBool nsHTMLMediaElement::CanHandleMediaType(const char* aMIMEType)
+{
+#ifdef MOZ_OGG
+  if (IsOggType(nsDependentCString(aMIMEType)))
+    return PR_TRUE;
+#endif
+  return PR_FALSE;
+}
+
+/* static */
+void nsHTMLMediaElement::InitMediaTypes()
+{
+  nsresult rv;
+  nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+  if (NS_SUCCEEDED(rv)) {
+#ifdef MOZ_OGG
+    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
+  }
+}
+
+/* static */
+void nsHTMLMediaElement::ShutdownMediaTypes()
+{
+  nsresult rv;
+  nsCOMPtr<nsICategoryManager> catMan(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+  if (NS_SUCCEEDED(rv)) {
+#ifdef MOZ_OGG
+    for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gOggTypes); i++) {
+      catMan->DeleteCategoryEntry("Gecko-Content-Viewers", gOggTypes[i], PR_FALSE);
+    }
+#endif
+  }
+}
+
+PRBool nsHTMLMediaElement::CreateDecoder(const nsACString& aType)
+{
+#ifdef MOZ_OGG
+  if (IsOggType(aType)) {
+    mDecoder = new nsOggDecoder();
+    if (mDecoder && !mDecoder->Init()) {
+      mDecoder = nsnull;
+    }
+  }
+#endif
+  return mDecoder != nsnull;
+}
+
+nsresult nsHTMLMediaElement::InitializeDecoderForChannel(nsIChannel *aChannel,
+                                                         nsIStreamListener **aListener)
+{
+  nsCAutoString mimeType;
+  aChannel->GetContentType(mimeType);
+
+  if (!CreateDecoder(mimeType))
+    return NS_ERROR_FAILURE;
+
+  mNetworkState = nsIDOMHTMLMediaElement::LOADING;
+  mDecoder->ElementAvailable(this);
+  
+  return mDecoder->Load(nsnull, aChannel, aListener);
+}
+
+nsresult nsHTMLMediaElement::PickMediaElement()
 {
   // Implements:
   // http://www.whatwg.org/specs/web-apps/current-work/#pick-a
   nsAutoString src;
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
     if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
-      aChosenMediaResource = src;
-
 #ifdef MOZ_OGG
       // Currently assuming an Ogg file
       // TODO: Instantiate decoder based on type
       if (mDecoder) {
         mDecoder->ElementUnavailable();
         mDecoder->Shutdown();
         mDecoder = nsnull;
       }
 
       mDecoder = new nsOggDecoder();
       if (mDecoder && !mDecoder->Init()) {
         mDecoder = nsnull;
       }
 #endif
-      return NS_OK;
+      return InitializeDecoder(src);
     }
   }
 
   // Checking of 'source' elements as per:
   // http://www.whatwg.org/specs/web-apps/current-work/#pick-a
   PRUint32 count = GetChildCount();
   for (PRUint32 i = 0; i < count; ++i) {
     nsIContent* child = GetChildAt(i);
     NS_ASSERTION(child, "GetChildCount lied!");
     
     nsCOMPtr<nsIContent> source = do_QueryInterface(child);
     if (source) {
       if (source->HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
         nsAutoString type;
-
-        if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
-#if MOZ_OGG
-          if (type.EqualsLiteral("video/ogg") || type.EqualsLiteral("application/ogg")) {
-            nsAutoString src;
-            if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
-              mDecoder = new nsOggDecoder();
-              if (mDecoder && !mDecoder->Init()) {
-                mDecoder = nsnull;
-              }
-              aChosenMediaResource = src;
-              return NS_OK;
-            }
-          }
-#endif
-        }
+        nsAutoString src;
+        if (source->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
+            source->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src) &&
+            CreateDecoder(NS_ConvertUTF16toUTF8(type)))
+          return InitializeDecoder(src);
       }
-    }    
-  }        
+    }
+  }
 
   return NS_ERROR_DOM_INVALID_STATE_ERR;
 }
 
-nsresult nsHTMLMediaElement::InitializeDecoder(nsAString& aChosenMediaResource)
+nsresult nsHTMLMediaElement::InitializeDecoder(const nsAString& aURISpec)
 {
+  mNetworkState = nsIDOMHTMLMediaElement::LOADING;
+
   nsCOMPtr<nsIDocument> doc = GetOwnerDoc();
   if (!doc) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   nsresult rv;
   nsCOMPtr<nsIURI> uri;
   nsCOMPtr<nsIURI> baseURL = GetBaseURI();
   const nsAFlatCString &charset = doc->GetDocumentCharacterSet();
-  rv = NS_NewURI(getter_AddRefs(uri), 
-                 aChosenMediaResource, 
+  rv = NS_NewURI(getter_AddRefs(uri), aURISpec,
                  charset.IsEmpty() ? nsnull : charset.get(), 
                  baseURL, 
                  nsContentUtils::GetIOService());
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mDecoder) {
-    rv = mDecoder->Load(uri);
+    mDecoder->ElementAvailable(this);
+    rv = mDecoder->Load(uri, nsnull, nsnull);
     if (NS_FAILED(rv)) {
       mDecoder = nsnull;
     }
   }
   return rv;
 }
 
 void nsHTMLMediaElement::MetadataLoaded()
--- a/content/html/content/src/nsHTMLVideoElement.cpp
+++ b/content/html/content/src/nsHTMLVideoElement.cpp
@@ -129,17 +129,8 @@ nsresult nsHTMLVideoElement::BindToTree(
 void nsHTMLVideoElement::UnbindFromTree(PRBool aDeep,
                                         PRBool aNullParent)
 {
   nsHTMLMediaElement::UnbindFromTree(aDeep, aNullParent);
 
   if (mDecoder) 
     mDecoder->ElementUnavailable();
 }
-
-nsresult nsHTMLVideoElement::InitializeDecoder(nsAString& aChosenMediaResource)
-{
-  if (mDecoder) 
-    mDecoder->ElementAvailable(this);
-
-  return nsHTMLMediaElement::InitializeDecoder(aChosenMediaResource);
-}
-
--- a/content/html/document/src/Makefile.in
+++ b/content/html/document/src/Makefile.in
@@ -76,23 +76,27 @@ REQUIRES	= xpcom \
 		  plugin \
 		  txtsvc \
 		  $(NULL)
 
 CPPSRCS		= \
 		nsHTMLContentSink.cpp \
 		nsHTMLFragmentContentSink.cpp \
 		nsHTMLDocument.cpp \
+		nsImageDocument.cpp \
 		nsMediaDocument.cpp \
 		nsPluginDocument.cpp \
-		nsImageDocument.cpp \
 		nsWyciwygChannel.cpp \
 		nsWyciwygProtocolHandler.cpp \
 		$(NULL)
 
+ifdef MOZ_MEDIA
+CPPSRCS += nsVideoDocument.cpp
+endif
+
 EXPORTS		= \
 		nsIHTMLDocument.h \
 		$(NULL)
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
--- a/content/html/document/src/nsImageDocument.cpp
+++ b/content/html/document/src/nsImageDocument.cpp
@@ -115,17 +115,17 @@ public:
   // imgIDecoderObserver (override nsStubImageDecoderObserver)
   NS_IMETHOD OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage);
 
   // nsIDOMEventListener
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
 
   friend class ImageListener;
 protected:
-  nsresult CreateSyntheticDocument();
+  virtual nsresult CreateSyntheticDocument();
 
   nsresult CheckOverflowing(PRBool changeState);
 
   void UpdateTitleAndCharset();
 
   nsresult ScrollImageTo(PRInt32 aX, PRInt32 aY, PRBool restoreImage);
 
   float GetRatio() {
--- a/content/html/document/src/nsMediaDocument.h
+++ b/content/html/document/src/nsMediaDocument.h
@@ -100,15 +100,14 @@ public:
   void SetStreamListener(nsIStreamListener *aListener);
 
   NS_DECL_ISUPPORTS
 
   NS_DECL_NSIREQUESTOBSERVER
 
   NS_DECL_NSISTREAMLISTENER
 
-protected:
   nsRefPtr<nsMediaDocument>    mDocument;
   nsCOMPtr<nsIStreamListener>  mNextStream;
 };
 
 
 #endif /* nsMediaDocument_h___ */
new file mode 100644
--- /dev/null
+++ b/content/html/document/src/nsVideoDocument.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * 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 "nsMediaDocument.h"
+#include "nsGkAtoms.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+#include "nsHTMLMediaElement.h"
+
+class nsVideoDocument : public nsMediaDocument
+{
+public:
+  virtual nsresult StartDocumentLoad(const char*         aCommand,
+                                     nsIChannel*         aChannel,
+                                     nsILoadGroup*       aLoadGroup,
+                                     nsISupports*        aContainer,
+                                     nsIStreamListener** aDocListener,
+                                     PRBool              aReset = PR_TRUE,
+                                     nsIContentSink*     aSink = nsnull);
+
+protected:
+  nsresult CreateSyntheticVideoDocument(nsIChannel* aChannel,
+                                        nsIStreamListener** aListener);
+
+  nsRefPtr<nsMediaDocumentStreamListener> mStreamListener;
+};
+
+nsresult
+nsVideoDocument::StartDocumentLoad(const char*         aCommand,
+                                   nsIChannel*         aChannel,
+                                   nsILoadGroup*       aLoadGroup,
+                                   nsISupports*        aContainer,
+                                   nsIStreamListener** aDocListener,
+                                   PRBool              aReset,
+                                   nsIContentSink*     aSink)
+{
+  nsresult rv =
+    nsMediaDocument::StartDocumentLoad(aCommand, aChannel, aLoadGroup,
+                                       aContainer, aDocListener, aReset,
+                                       aSink);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mStreamListener = new nsMediaDocumentStreamListener(this);
+  if (!mStreamListener)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  // Create synthetic document
+  rv = CreateSyntheticVideoDocument(aChannel,
+      getter_AddRefs(mStreamListener->mNextStream));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  NS_ADDREF(*aDocListener = mStreamListener);
+  return rv;
+}
+
+nsresult
+nsVideoDocument::CreateSyntheticVideoDocument(nsIChannel* aChannel,
+                                              nsIStreamListener** aListener)
+{
+  // make our generic document
+  nsresult rv = nsMediaDocument::CreateSyntheticDocument();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsIContent* body = GetBodyContent();
+  if (!body) {
+    NS_WARNING("no body on video document!");
+    return NS_ERROR_FAILURE;
+  }
+
+  // make content
+  nsCOMPtr<nsINodeInfo> nodeInfo;
+  nodeInfo = mNodeInfoManager->GetNodeInfo(nsGkAtoms::video, nsnull,
+                                           kNameSpaceID_None);
+  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_FAILURE);
+
+  nsRefPtr<nsHTMLMediaElement> element =
+    static_cast<nsHTMLMediaElement*>(NS_NewHTMLVideoElement(nodeInfo, PR_FALSE));
+  if (!element)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  element->SetAutoplay(PR_TRUE);
+  element->SetControls(PR_TRUE);
+  element->LoadWithChannel(aChannel, aListener);
+
+  return body->AppendChildTo(element, PR_FALSE);
+}
+
+nsresult
+NS_NewVideoDocument(nsIDocument** aResult)
+{
+  nsVideoDocument* doc = new nsVideoDocument();
+  if (!doc) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  NS_ADDREF(doc);
+  nsresult rv = doc->Init();
+
+  if (NS_FAILED(rv)) {
+    NS_RELEASE(doc);
+  }
+
+  *aResult = doc;
+
+  return rv;
+}
--- a/content/media/video/public/nsChannelReader.h
+++ b/content/media/video/public/nsChannelReader.h
@@ -39,24 +39,34 @@
 
 #include "nsAutoPtr.h"
 #include "nsMediaStream.h"
 #include "nsMediaDecoder.h"
 #include "nsIPrincipal.h"
 
 #include "oggplay/oggplay.h"
 
+class nsIChannel;
+class nsIStreamListener;
+
 class nsChannelReader : public OggPlayReader
 {
 public:
   nsChannelReader();
   ~nsChannelReader();
 
-  // Initialize the reader with the given decoder and URI.
-  nsresult Init(nsMediaDecoder* aDecoder, nsIURI* aURI);
+  /**
+   * Initialize the reader with the given decoder, URI, and
+   * optional channel.
+   * @param aChannel may be null
+   * @param aStreamListener if aChannel is non-null, this will return
+   * a stream listener which should be attached to the channel.
+   */
+  nsresult Init(nsMediaDecoder* aDecoder, nsIURI* aURI, nsIChannel* aChannel,
+                nsIStreamListener** aStreamListener);
 
   // Cancel any blocking request currently in progress and cause that
   // request to return an error. Call on main thread only.
   void Cancel();
 
   // Return the number of bytes buffered from the file. This can safely
   // be read without blocking.
   PRUint32 Available();
--- a/content/media/video/public/nsMediaDecoder.h
+++ b/content/media/video/public/nsMediaDecoder.h
@@ -104,20 +104,23 @@ class nsMediaDecoder : public nsIObserve
 
   // Start playback of a video. 'Load' must have previously been
   // called.
   virtual nsresult Play() = 0;
 
   // Stop playback of a video, and stop download of video stream.
   virtual void Stop() = 0;
 
-  // Start downloading the video at the given URI. Decode
-  // the downloaded data up to the point of the first frame
-  // of data. 
-  virtual nsresult Load(nsIURI* aURI) = 0;
+  // Start downloading the video. Decode the downloaded data up to the
+  // point of the first frame of data.
+  // Exactly one of aURI and aChannel must be null. aListener must be
+  // null if and only if aChannel is.
+  virtual nsresult Load(nsIURI* aURI,
+                        nsIChannel* aChannel,
+                        nsIStreamListener **aListener) = 0;
 
   // Draw the latest video data. This is done
   // here instead of in nsVideoFrame so that the lock around the
   // RGB buffer doesn't have to be exposed publically.
   // The current video frame is drawn to fill aRect.
   // Called in the main thread only.
   virtual void Paint(gfxContext* aContext, const gfxRect& aRect);
 
--- a/content/media/video/public/nsMediaStream.h
+++ b/content/media/video/public/nsMediaStream.h
@@ -65,17 +65,24 @@ public:
   virtual ~nsStreamStrategy()
   {
     PR_DestroyLock(mLock);
     MOZ_COUNT_DTOR(nsStreamStrategy);
   }
 
   // These methods have the same thread calling requirements 
   // as those with the same name in nsMediaStream
-  virtual nsresult Open() = 0;
+
+  /**
+   * @param aStreamListener if null, the strategy should open mChannel
+   * itself. Otherwise, mChannel is already open and the strategy
+   * should just return its stream listener in aStreamListener (or set
+   * *aStreamListener to null, if it doesn't need a listener).
+   */
+  virtual nsresult Open(nsIStreamListener** aStreamListener) = 0;
   virtual nsresult Close() = 0;
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes) = 0;
   virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset) = 0;
   virtual PRInt64  Tell() = 0;
   virtual PRUint32 Available() = 0;
   virtual float    DownloadRate() = 0;
   virtual void     Cancel() { }
   virtual nsIPrincipal* GetCurrentPrincipal() = 0;
@@ -116,19 +123,25 @@ protected:
    methods once Close is called.
 */
 class nsMediaStream
 {
  public:
   nsMediaStream();
   ~nsMediaStream();
 
-  // Create a channel for the stream, reading data from the 
-  // media resource at the URI. Call on main thread only.
-  nsresult Open(nsMediaDecoder *aDecoder, nsIURI* aURI);
+  /**
+   * Create a channel for the stream, reading data from the 
+   * media resource at the URI. Call on main thread only.
+   * @param aChannel if non-null, this channel is used and aListener
+   * is set to the listener we want for the channel. aURI must
+   * be the URI for the channel, obtained via NS_GetFinalChannelURI.
+   */
+  nsresult Open(nsMediaDecoder* aDecoder, nsIURI* aURI,
+                nsIChannel* aChannel, nsIStreamListener** aListener);
 
   // Close the stream, stop any listeners, channels, etc.
   // Call on main thread only.
   nsresult Close();
 
   // Read up to aCount bytes from the stream. The buffer must have
   // enough room for at least aCount bytes. Stores the number of
   // actual bytes read in aBytes (0 on end of file). Can be called
--- a/content/media/video/public/nsOggDecoder.h
+++ b/content/media/video/public/nsOggDecoder.h
@@ -292,44 +292,43 @@ class nsOggDecoder : public nsMediaDecod
   };
 
   nsOggDecoder();
   ~nsOggDecoder();
   PRBool Init();
 
   // This method must be called by the owning object before that
   // object disposes of this decoder object.
-  void Shutdown();
+  virtual void Shutdown();
   
-  float GetCurrentTime();
+  virtual float GetCurrentTime();
 
-  // Start downloading the video at the given URI. Decode
-  // the downloaded data up to the point of the first frame
-  // of data. 
-  nsresult Load(nsIURI* aURI);
+  virtual nsresult Load(nsIURI* aURI,
+                        nsIChannel* aChannel,
+                        nsIStreamListener **aListener);
 
   // Start playback of a video. 'Load' must have previously been
   // called.
-  nsresult Play();
+  virtual nsresult Play();
 
   // Stop playback of a video, and stop download of video stream.
   virtual void Stop();
 
   // Seek to the time position in (seconds) from the start of the video.
-  nsresult Seek(float time);
+  virtual nsresult Seek(float time);
 
-  nsresult PlaybackRateChanged();
+  virtual nsresult PlaybackRateChanged();
 
-  void Pause();
-  float GetVolume();
-  void SetVolume(float volume);
-  float GetDuration();
+  virtual void Pause();
+  virtual float GetVolume();
+  virtual void SetVolume(float volume);
+  virtual float GetDuration();
 
-  void GetCurrentURI(nsIURI** aURI);
-  nsIPrincipal* GetCurrentPrincipal();
+  virtual void GetCurrentURI(nsIURI** aURI);
+  virtual nsIPrincipal* GetCurrentPrincipal();
 
   virtual void UpdateBytesDownloaded(PRUint64 aBytes);
 
   // Called when the video file has completed downloading.
   // Call on the main thread only.
   void ResourceLoaded();
 
   // Call from any thread safely. Return PR_TRUE if we are currently
--- a/content/media/video/src/nsChannelReader.cpp
+++ b/content/media/video/src/nsChannelReader.cpp
@@ -142,20 +142,22 @@ static long oggplay_channel_reader_io_te
 }
 
 static int oggplay_channel_reader_duration(OggPlayReader* aReader) 
 {
   nsChannelReader* me = static_cast<nsChannelReader*>(aReader);
   return me->duration();
 }
 
-nsresult nsChannelReader::Init(nsMediaDecoder* aDecoder, nsIURI* aURI)
+nsresult nsChannelReader::Init(nsMediaDecoder* aDecoder, nsIURI* aURI,
+                               nsIChannel* aChannel,
+                               nsIStreamListener** aStreamListener)
 {
   mCurrentPosition = 0;
-  return mStream.Open(aDecoder, aURI);
+  return mStream.Open(aDecoder, aURI, aChannel, aStreamListener);
 }
 
 nsChannelReader::~nsChannelReader()
 {
   MOZ_COUNT_DTOR(nsChannelReader);
 }
 
 nsChannelReader::nsChannelReader() 
--- a/content/media/video/src/nsMediaStream.cpp
+++ b/content/media/video/src/nsMediaStream.cpp
@@ -62,17 +62,17 @@ class nsDefaultStreamStrategy : public n
 public:
   nsDefaultStreamStrategy(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
     nsStreamStrategy(aDecoder, aChannel, aURI)
   {
   }
   
   // These methods have the same thread calling requirements 
   // as those with the same name in nsMediaStream
-  virtual nsresult Open();
+  virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult Close();
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
   virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
   virtual PRInt64  Tell();
   virtual PRUint32 Available();
   virtual float    DownloadRate();
   virtual void     Cancel();
   virtual nsIPrincipal* GetCurrentPrincipal();
@@ -84,29 +84,36 @@ private:
   // main thread only.
   nsCOMPtr<nsChannelToPipeListener> mListener;
 
   // Input stream for the media data currently downloaded 
   // and stored in the pipe. This can be used from any thread.
   nsCOMPtr<nsIInputStream>  mPipeInput;
 };
 
-nsresult nsDefaultStreamStrategy::Open()
+nsresult nsDefaultStreamStrategy::Open(nsIStreamListener** aStreamListener)
 {
-  nsresult rv;
+  if (aStreamListener) {
+    *aStreamListener = nsnull;
+  }
 
   mListener = new nsChannelToPipeListener(mDecoder);
   NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
 
-  rv = mListener->Init();
+  nsresult rv = mListener->Init();
   NS_ENSURE_SUCCESS(rv, rv);
-  
-  rv = mChannel->AsyncOpen(mListener, nsnull);
-  NS_ENSURE_SUCCESS(rv, rv);
-  
+
+  if (aStreamListener) {
+    *aStreamListener = mListener;
+    NS_ADDREF(mListener);
+  } else {
+    rv = mChannel->AsyncOpen(mListener, nsnull);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   rv = mListener->GetInputStream(getter_AddRefs(mPipeInput));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult nsDefaultStreamStrategy::Close()
 {
@@ -184,17 +191,17 @@ class nsFileStreamStrategy : public nsSt
 public:
   nsFileStreamStrategy(nsMediaDecoder* aDecoder, nsIChannel* aChannel, nsIURI* aURI) :
     nsStreamStrategy(aDecoder, aChannel, aURI)
   {
   }
   
   // These methods have the same thread calling requirements 
   // as those with the same name in nsMediaStream
-  virtual nsresult Open();
+  virtual nsresult Open(nsIStreamListener** aStreamListener);
   virtual nsresult Close();
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
   virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
   virtual PRInt64  Tell();
   virtual PRUint32 Available();
   virtual float    DownloadRate();
   virtual nsIPrincipal* GetCurrentPrincipal();
 
@@ -206,57 +213,76 @@ private:
   // Input stream for the media data. This can be used from any
   // thread.
   nsCOMPtr<nsIInputStream>  mInput;
 
   // Security Principal
   nsCOMPtr<nsIPrincipal> mPrincipal;
 };
 
-nsresult nsFileStreamStrategy::Open()
+nsresult nsFileStreamStrategy::Open(nsIStreamListener** aStreamListener)
 {
+  if (aStreamListener) {
+    *aStreamListener = nsnull;
+  }
+
   nsresult rv;
+  if (aStreamListener) {
+    // The channel is already open. We need a synchronous stream that
+    // implements nsISeekableStream, so we have to find the underlying
+    // file and reopen it
+    nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
+    if (!fc)
+      return NS_ERROR_UNEXPECTED;
 
-  rv = mChannel->Open(getter_AddRefs(mInput));
+    nsCOMPtr<nsIFile> file; 
+    rv = fc->GetFile(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
+  } else {
+    rv = mChannel->Open(getter_AddRefs(mInput));
+  }
   NS_ENSURE_SUCCESS(rv, rv);
 
   mSeekable = do_QueryInterface(mInput);
+  if (!mSeekable) {
+    // XXX The file may just be a .url or similar
+    // shortcut that points to a Web site. We need to fix this by
+    // doing an async open and waiting until we locate the real resource,
+    // then using that (if it's still a file!).
+    return NS_ERROR_FAILURE;
+  }
 
-  // Get the file size and inform the decoder
-  nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
-  if (fc) {
-    nsCOMPtr<nsIFile> file;
-    rv = fc->GetFile(getter_AddRefs(file));
-    if (NS_SUCCEEDED(rv)) {
-      PRInt64 size = 0;
-      rv = file->GetFileSize(&size);
-      if (NS_SUCCEEDED(rv)) {
-        mDecoder->SetTotalBytes(size);
-      }
-    }
+  // Get the file size and inform the decoder. Only files up to 4GB are
+  // supported here.
+  PRUint32 size;
+  rv = mInput->Available(&size);
+  if (NS_SUCCEEDED(rv)) {
+    mDecoder->SetTotalBytes(size);
   }
 
   /* Get our principal */
   nsCOMPtr<nsIScriptSecurityManager> secMan =
     do_GetService("@mozilla.org/scriptsecuritymanager;1");
   if (secMan) {
-    nsresult rv = secMan->GetChannelPrincipal(mChannel,
-                                              getter_AddRefs(mPrincipal));
+    rv = secMan->GetChannelPrincipal(mChannel,
+                                     getter_AddRefs(mPrincipal));
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
   // For a file stream the resource is considered loaded since there
   // is no buffering delays, etc reading.
   nsCOMPtr<nsIRunnable> event = 
     NS_NEW_RUNNABLE_METHOD(nsMediaDecoder, mDecoder, ResourceLoaded); 
   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
   
-  return mSeekable ? NS_OK : NS_ERROR_FAILURE;
+  return NS_OK;
 }
 
 nsresult nsFileStreamStrategy::Close()
 {
   nsAutoLock lock(mLock);
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannel = nsnull;
@@ -319,17 +345,17 @@ public:
     mPosition(0),
     mAtEOF(PR_FALSE),
     mCancelled(PR_FALSE)
   {
   }
   
   // These methods have the same thread calling requirements 
   // as those with the same name in nsMediaStream
-  virtual nsresult Open();
+  virtual nsresult Open(nsIStreamListener** aListener);
   virtual nsresult Close();
   virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
   virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
   virtual PRInt64  Tell();
   virtual PRUint32 Available();
   virtual float    DownloadRate();
   virtual void     Cancel();
   virtual nsIPrincipal* GetCurrentPrincipal();
@@ -378,28 +404,35 @@ void nsHttpStreamStrategy::Reset(nsIChan
                                  nsIInputStream* aStream)
 {
   nsAutoLock lock(mLock);
   mChannel = aChannel;
   mListener = aListener;
   mPipeInput = aStream;
 }
 
-nsresult nsHttpStreamStrategy::Open()
+nsresult nsHttpStreamStrategy::Open(nsIStreamListener **aStreamListener)
 {
-  nsresult rv;
+  if (aStreamListener) {
+    *aStreamListener = nsnull;
+  }
 
   mListener = new nsChannelToPipeListener(mDecoder);
   NS_ENSURE_TRUE(mListener, NS_ERROR_OUT_OF_MEMORY);
 
-  rv = mListener->Init();
+  nsresult rv = mListener->Init();
   NS_ENSURE_SUCCESS(rv, rv);
   
-  rv = mChannel->AsyncOpen(mListener, nsnull);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (aStreamListener) {
+    *aStreamListener = mListener;
+    NS_ADDREF(*aStreamListener);
+  } else {
+    rv = mChannel->AsyncOpen(mListener, nsnull);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
   
   rv = mListener->GetInputStream(getter_AddRefs(mPipeInput));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mPosition = 0;
 
   return NS_OK;
 }
@@ -679,45 +712,48 @@ nsMediaStream::nsMediaStream()  :
   MOZ_COUNT_CTOR(nsMediaStream);
 }
 
 nsMediaStream::~nsMediaStream()
 {
   MOZ_COUNT_DTOR(nsMediaStream);
 }
 
-nsresult nsMediaStream::Open(nsMediaDecoder* aDecoder, nsIURI* aURI)
+nsresult nsMediaStream::Open(nsMediaDecoder* aDecoder, nsIURI* aURI,
+                             nsIChannel* aChannel, nsIStreamListener** aListener)
 {
   NS_ASSERTION(NS_IsMainThread(), 
 	       "nsMediaStream::Open called on non-main thread");
 
-  nsresult rv;
-
   nsCOMPtr<nsIChannel> channel;
-  rv = NS_NewChannel(getter_AddRefs(channel), 
-                     aURI, 
-                     nsnull,
-                     nsnull,
-                     nsnull,
-                     nsIRequest::LOAD_NORMAL);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (aChannel) {
+    channel = aChannel;
+  } else {
+    nsresult rv = NS_NewChannel(getter_AddRefs(channel), 
+                                aURI, 
+                                nsnull,
+                                nsnull,
+                                nsnull,
+                                nsIRequest::LOAD_NORMAL);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(channel);
   nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
   if (hc) 
     mStreamStrategy = new nsHttpStreamStrategy(aDecoder, channel, aURI);
   else if (fc) 
     mStreamStrategy = new nsFileStreamStrategy(aDecoder, channel, aURI);
   else
     mStreamStrategy = new nsDefaultStreamStrategy(aDecoder, channel, aURI);
 
   mPlaybackRateCount = 0;
   mPlaybackRateStart = PR_IntervalNow();
 
-  return mStreamStrategy->Open();
+  return mStreamStrategy->Open(aListener);
 }
 
 nsresult nsMediaStream::Close()
 {
   NS_ASSERTION(NS_IsMainThread(), 
 	       "nsMediaStream::Close called on non-main thread");
 
   return mStreamStrategy->Close();
--- a/content/media/video/src/nsOggDecoder.cpp
+++ b/content/media/video/src/nsOggDecoder.cpp
@@ -44,16 +44,17 @@
 #include "nsNetUtil.h"
 #include "nsAudioStream.h"
 #include "nsChannelReader.h"
 #include "nsHTMLVideoElement.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsAutoLock.h"
 #include "nsTArray.h"
+#include "nsNetUtil.h"
 #include "nsOggDecoder.h"
 
 /* 
    The maximum height and width of the video. Used for
    sanitizing the memory allocation of the RGB buffer
 */
 #define MAX_VIDEO_WIDTH  2000
 #define MAX_VIDEO_HEIGHT 2000
@@ -1160,29 +1161,45 @@ void nsOggDecoder::Shutdown()
 }
 
 nsOggDecoder::~nsOggDecoder()
 {
   MOZ_COUNT_DTOR(nsOggDecoder);
   nsAutoMonitor::DestroyMonitor(mMonitor);
 }
 
-nsresult nsOggDecoder::Load(nsIURI* aURI) 
+nsresult nsOggDecoder::Load(nsIURI* aURI, nsIChannel* aChannel,
+                            nsIStreamListener** aStreamListener)
 {
-  nsresult rv;
-  mURI = aURI;
+  if (aStreamListener) {
+    *aStreamListener = nsnull;
+  }
+
+  if (aURI) {
+    NS_ASSERTION(!aStreamListener, "No listener should be requested here");
+    mURI = aURI;
+  } else {
+    NS_ASSERTION(aChannel, "Either a URI or a channel is required");
+    NS_ASSERTION(aStreamListener, "A listener should be requested here");
+
+    // If the channel was redirected, we want the post-redirect URI;
+    // but if the URI scheme was expanded, say from chrome: to jar:file:,
+    // we want the original URI.
+    nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(mURI));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
 
   StartProgress();
 
   RegisterShutdownObserver();
 
   mReader = new nsChannelReader();
   NS_ENSURE_TRUE(mReader, NS_ERROR_OUT_OF_MEMORY);
 
-  rv = mReader->Init(this, aURI);
+  nsresult rv = mReader->Init(this, mURI, aChannel, aStreamListener);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = NS_NewThread(getter_AddRefs(mDecodeThread));
   NS_ENSURE_SUCCESS(rv, rv);
 
   mDecodeStateMachine = new nsOggDecodeStateMachine(this, mReader);
 
   ChangeState(PLAY_STATE_LOADING);
@@ -1452,19 +1469,22 @@ void nsOggDecoder::ChangeState(PlayState
     return;
   }
 
   if (mPlayState == PLAY_STATE_ENDED &&
       aState != PLAY_STATE_SHUTDOWN) {
     // If we've completed playback then the decode and display threads
     // have been shutdown. To honor the state change request we need
     // to reload the resource and restart the threads.
+    // Like seeking, this will require opening a new channel, which means
+    // we may not actually get the same resource --- a server may send
+    // us something different.
     mNextState = aState;
     mPlayState = PLAY_STATE_LOADING;
-    Load(mURI);
+    Load(mURI, nsnull, nsnull);
     return;
   }
 
   mPlayState = aState;
   switch (aState) {
   case PLAY_STATE_PAUSED:
     /* No action needed */
     break;
--- a/content/media/video/test/Makefile.in
+++ b/content/media/video/test/Makefile.in
@@ -59,16 +59,17 @@ include $(topsrcdir)/config/rules.mk
                 test_seek1.html \
                 test_seek2.html \
                 test_seek3.html \
                 test_seek4.html \
                 test_seek5.html \
                 test_seek6.html \
                 test_seek7.html \
                 test_seek8.html \
+                test_standalone.html \
                 test_timeupdate1.html \
                 test_timeupdate2.html \
                 test_timeupdate3.html \
                 test_volume.html \
                 320x240.ogg \
                 bug461281.ogg \
                 seek.ogg \
 #                test_bug448534.html \
new file mode 100644
--- /dev/null
+++ b/content/media/video/test/test_standalone.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Media test: standalone video documents</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 onload="doTest()">
+
+<iframe id="i" src="320x240.ogg"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function doTest()
+{
+  var elem = document.getElementById("i").contentDocument.body.firstChild;
+  is(elem.tagName.toLowerCase(), "video", "Is video element");
+  is(elem.currentSrc.substring(elem.currentSrc.length - 11), "320x240.ogg", "currentSrc");
+  is(elem.controls, true, "Controls set");
+  is(elem.autoplay, true, "Autoplay set");
+  SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -155,16 +155,17 @@ SHARED_LIBRARY_LIBS 	+= \
 	$(DEPTH)/media/libfishsound/src/libfishsound/$(LIB_PREFIX)fishsound.$(LIB_SUFFIX) \
 	$(DEPTH)/media/libogg/src/$(LIB_PREFIX)ogg.$(LIB_SUFFIX) \
 	$(DEPTH)/media/liboggplay/src/liboggplay/$(LIB_PREFIX)oggplay.$(LIB_SUFFIX) \
 	$(DEPTH)/media/liboggz/src/liboggz/$(LIB_PREFIX)oggz.$(LIB_SUFFIX) \
 	$(DEPTH)/media/libsydneyaudio/src/$(LIB_PREFIX)sydneyaudio.$(LIB_SUFFIX) \
 	$(DEPTH)/media/libtheora/lib/$(LIB_PREFIX)theora.$(LIB_SUFFIX) \
 	$(DEPTH)/media/libvorbis/lib/$(LIB_PREFIX)vorbis.$(LIB_SUFFIX) \
 	$(NULL)
+LOCAL_INCLUDES += -I$(DEPTH)/content/html/content/src
 endif
 
 ifdef NS_PRINTING
 SHARED_LIBRARY_LIBS += \
 		../printing/$(LIB_PREFIX)gkprinting_s.$(LIB_SUFFIX) \
 		$(NULL)
 endif
 
--- a/layout/build/nsContentDLF.cpp
+++ b/layout/build/nsContentDLF.cpp
@@ -51,16 +51,19 @@
 #include "nsNodeInfoManager.h"
 #include "nsString.h"
 #include "nsContentCID.h"
 #include "prprf.h"
 #include "nsNetUtil.h"
 #include "nsICSSLoader.h"
 #include "nsCRT.h"
 #include "nsIViewSourceChannel.h"
+#ifdef MOZ_MEDIA
+#include "nsHTMLMediaElement.h"
+#endif
 
 #include "imgILoader.h"
 #include "nsIParser.h"
 
 // plugins
 #include "nsIPluginManager.h"
 #include "nsIPluginHost.h"
 static NS_DEFINE_CID(kPluginManagerCID, NS_PLUGINMANAGER_CID);
@@ -73,16 +76,19 @@ static NS_DEFINE_CID(kPluginDocumentCID,
 
 #undef NOISY_REGISTRY
 
 static NS_DEFINE_IID(kHTMLDocumentCID, NS_HTMLDOCUMENT_CID);
 static NS_DEFINE_IID(kXMLDocumentCID, NS_XMLDOCUMENT_CID);
 #ifdef MOZ_SVG
 static NS_DEFINE_IID(kSVGDocumentCID, NS_SVGDOCUMENT_CID);
 #endif
+#ifdef MOZ_MEDIA
+static NS_DEFINE_IID(kVideoDocumentCID, NS_VIDEODOCUMENT_CID);
+#endif
 static NS_DEFINE_IID(kImageDocumentCID, NS_IMAGEDOCUMENT_CID);
 static NS_DEFINE_IID(kXULDocumentCID, NS_XULDOCUMENT_CID);
 
 nsresult
 NS_NewDocumentViewer(nsIDocumentViewer** aResult);
 
 // XXXbz if you change the MIME types here, be sure to update
 // nsIParser.h and DetermineParseMode in nsParser.cpp accordingly.
@@ -262,16 +268,25 @@ nsContentDLF::CreateInstance(const char*
     if (0 == PL_strcmp(gXULTypes[typeIndex++], aContentType)) {
       return CreateXULDocument(aCommand, 
                                aChannel, aLoadGroup,
                                aContentType, aContainer,
                                aExtraInfo, aDocListener, aDocViewer);
     }
   }
 
+#ifdef MOZ_MEDIA
+  if (nsHTMLMediaElement::CanHandleMediaType(aContentType)) {
+    return CreateDocument(aCommand, 
+                          aChannel, aLoadGroup,
+                          aContainer, kVideoDocumentCID,
+                          aDocListener, aDocViewer);
+  }  
+#endif
+
   // Try image types
   nsCOMPtr<imgILoader> loader(do_GetService("@mozilla.org/image/loader;1"));
   PRBool isReg = PR_FALSE;
   loader->SupportImageWithMimeType(aContentType, &isReg);
   if (isReg) {
     return CreateDocument(aCommand, 
                           aChannel, aLoadGroup,
                           aContainer, kImageDocumentCID,
@@ -281,17 +296,16 @@ nsContentDLF::CreateInstance(const char*
   nsCOMPtr<nsIPluginHost> ph (do_GetService(kPluginManagerCID));
   if(ph && NS_SUCCEEDED(ph->IsPluginEnabledForType(aContentType))) {
     return CreateDocument(aCommand,
                           aChannel, aLoadGroup,
                           aContainer, kPluginDocumentCID,
                           aDocListener, aDocViewer);
   }
 
-
   // If we get here, then we weren't able to create anything. Sorry!
   return NS_ERROR_FAILURE;
 }
 
 
 NS_IMETHODIMP
 nsContentDLF::CreateInstanceForDocument(nsISupports* aContainer,
                                         nsIDocument* aDocument,
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -525,16 +525,19 @@ MAKE_CTOR(CreateContentDLF,             
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsCSSOMFactory)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsInspectorCSSUtils)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsWyciwygProtocolHandler)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsContentAreaDragDrop)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDataDocumentContentPolicy)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsNoDataProtocolContentPolicy)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSyncLoadService)
 MAKE_CTOR(CreatePluginDocument,           nsIDocument,                 NS_NewPluginDocument)
+#ifdef MOZ_MEDIA
+MAKE_CTOR(CreateVideoDocument,            nsIDocument,                 NS_NewVideoDocument)
+#endif
 
 #ifdef MOZ_ENABLE_CANVAS
 MAKE_CTOR(CreateCanvasRenderingContext2D, nsIDOMCanvasRenderingContext2D, NS_NewCanvasRenderingContext2D)
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsStyleSheetService, Init)
 
 // views are not refcounted, so this is the same as
@@ -1277,16 +1280,23 @@ static const nsModuleComponentInfo gComp
     "@mozilla.org/content/plugin/document-loader-factory;1",
     CreateContentDLF },
 
   { "Plugin Document",
     NS_PLUGINDOCUMENT_CID,
     nsnull,
     CreatePluginDocument },
 
+#ifdef MOZ_MEDIA
+  { "Video Document",
+    NS_VIDEODOCUMENT_CID,
+    nsnull,
+    CreateVideoDocument },
+#endif
+
   { "Style sheet service",
     NS_STYLESHEETSERVICE_CID,
     NS_STYLESHEETSERVICE_CONTRACTID,
     nsStyleSheetServiceConstructor },
 
   // transformiix
 
   { "XSLTProcessor",
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -107,16 +107,17 @@ PRBool NS_SVGEnabled();
 
 #ifndef MOZILLA_PLAINTEXT_EDITOR_ONLY
 #include "nsHTMLEditor.h"
 #include "nsTextServicesDocument.h"
 #endif
 
 #ifdef MOZ_MEDIA
 #include "nsMediaDecoder.h"
+#include "nsHTMLMediaElement.h"
 #endif
 
 #ifdef MOZ_OGG
 #include "nsAudioStream.h"
 #endif
 
 #include "nsError.h"
 #include "nsTraceRefcnt.h"
@@ -248,16 +249,17 @@ nsLayoutStatics::Initialize()
 
 #ifdef MOZ_MEDIA
   rv = nsMediaDecoder::InitLogger();
   if (NS_FAILED(rv)) {
     NS_ERROR("Could not initialize nsMediaDecoder");
     return rv;
   }
   
+  nsHTMLMediaElement::InitMediaTypes();
 #endif
 
 #ifdef MOZ_OGG
   nsAudioStream::InitLibrary();
 #endif
 
   return NS_OK;
 }
@@ -334,16 +336,19 @@ nsLayoutStatics::Shutdown()
   nsHTMLEditor::Shutdown();
   nsTextServicesDocument::Shutdown();
 #endif
 
   nsDOMThreadService::Shutdown();
 
   NS_ShutdownFocusSuppressor();
 
+#ifdef MOZ_MEDIA
+  nsHTMLMediaElement::ShutdownMediaTypes();
+#endif
 #ifdef MOZ_OGG
   nsAudioStream::ShutdownLibrary();
 #endif
 
   nsXMLHttpRequest::ShutdownACCache();
 }
 
 void
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -530,16 +530,19 @@ static nsExtraMimeTypeEntry extraMimeEnt
   { MESSAGE_RFC822, "eml", "RFC-822 data", MAC_TYPE('TEXT'), MAC_TYPE('MOSS') },
   { TEXT_PLAIN, "txt,text", "Text File", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
   { TEXT_HTML, "html,htm,shtml,ehtml", "HyperText Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('MOSS') },
   { "application/xhtml+xml", "xhtml,xht", "Extensible HyperText Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
   { APPLICATION_RDF, "rdf", "Resource Description Framework", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
   { TEXT_XUL, "xul", "XML-Based User Interface Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
   { TEXT_XML, "xml,xsl,xbl", "Extensible Markup Language", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
   { TEXT_CSS, "css", "Style Sheet", MAC_TYPE('TEXT'), MAC_TYPE('ttxt') },
+  { "audio/ogg", "oga", "Ogg Audio", 0, 0 },
+  { "video/ogg", "ogv", "Ogg Video", 0, 0 },
+  { "audio/ogg", "ogg", "Ogg Audio", 0, 0 }
 };
 
 #undef MAC_TYPE
 
 /**
  * File extensions for which decoding should be disabled.
  * NOTE: These MUST be lower-case and ASCII.
  */