Bug 518659. Make nsHTMLMediaElement hold a reference to itself to keep itself alive while network or decoder activity could result in events being fired in the future. Also rework the handling of media elements in inactive documents. r=jst, r=doublec
authorRobert O'Callahan <robert@ocallahan.org>
Fri, 02 Oct 2009 00:10:13 +1000
changeset 34207 e79927f90d0bc2d2ad1a80adf5148e03905d46c3
parent 34206 d71007568c2410e039d7ce8c848a4ab5993efb9d
child 34208 52c525d3a38451d8963e371190e06253514b519c
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, doublec
bugs518659
milestone1.9.3a1pre
Bug 518659. Make nsHTMLMediaElement hold a reference to itself to keep itself alive while network or decoder activity could result in events being fired in the future. Also rework the handling of media elements in inactive documents. r=jst, r=doublec
content/base/public/nsIDocument.h
content/base/src/nsDocument.cpp
content/base/src/nsNodeUtils.cpp
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLMediaElement.cpp
content/media/nsMediaDecoder.h
content/media/test/Makefile.in
content/media/test/reactivate_helper.html
content/media/test/test_reactivate.html
layout/base/nsPresShell.cpp
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -686,22 +686,20 @@ public:
    * Reset this document to aURI, aLoadGroup, and aPrincipal.  aURI must not be
    * null.  If aPrincipal is null, a codebase principal based on aURI will be
    * used.
    */
   virtual void ResetToURI(nsIURI *aURI, nsILoadGroup* aLoadGroup,
                           nsIPrincipal* aPrincipal) = 0;
 
   /**
-   * Set the container (docshell) for this document.
+   * Set the container (docshell) for this document. Virtual so that
+   * docshell can call it.
    */
-  void SetContainer(nsISupports *aContainer)
-  {
-    mDocumentContainer = do_GetWeakReference(aContainer);
-  }
+  virtual void SetContainer(nsISupports *aContainer);
 
   /**
    * Get the container (docshell) for this document.
    */
   already_AddRefed<nsISupports> GetContainer() const
   {
     nsISupports* container = nsnull;
     if (mDocumentContainer)
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -164,16 +164,17 @@ static NS_DEFINE_CID(kDOMEventGroupCID, 
 #include "nsIContentViewer.h"
 #include "nsIXMLContentSink.h"
 #include "nsContentErrors.h"
 #include "nsIXULDocument.h"
 #include "nsIPrompt.h"
 #include "nsIPropertyBag2.h"
 #include "nsIDOMPageTransitionEvent.h"
 #include "nsFrameLoader.h"
+#include "nsHTMLMediaElement.h"
 
 #include "mozAutoDocUpdate.h"
 
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #include "imgIContainer.h"
 #include "nsSVGUtils.h"
 #endif // MOZ_SMIL
@@ -3569,16 +3570,35 @@ nsDocument::GetScriptGlobalObject() cons
 
 nsIScriptGlobalObject*
 nsDocument::GetScopeObject()
 {
   nsCOMPtr<nsIScriptGlobalObject> scope(do_QueryReferent(mScopeObject));
   return scope;
 }
 
+static void
+NotifyActivityChanged(nsIContent *aContent, void *aUnused)
+{
+#ifdef MOZ_MEDIA
+  nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aContent));
+  if (domMediaElem) {
+    nsHTMLMediaElement* mediaElem = static_cast<nsHTMLMediaElement*>(aContent);
+    mediaElem->NotifyOwnerDocumentActivityChanged();
+  }
+#endif
+}
+
+void
+nsIDocument::SetContainer(nsISupports* aContainer)
+{
+  mDocumentContainer = do_GetWeakReference(aContainer);
+  EnumerateFreezableElements(NotifyActivityChanged, nsnull);
+}
+
 void
 nsDocument::SetScriptGlobalObject(nsIScriptGlobalObject *aScriptGlobalObject)
 {
 #ifdef DEBUG
   {
     nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(aScriptGlobalObject));
 
     NS_ASSERTION(!win || win->IsInnerWindow(),
@@ -6967,16 +6987,17 @@ nsDocument::Destroy()
 
 void
 nsDocument::RemovedFromDocShell()
 {
   if (mRemovedFromDocShell)
     return;
 
   mRemovedFromDocShell = PR_TRUE;
+  EnumerateFreezableElements(NotifyActivityChanged, nsnull); 
 
   PRUint32 i, count = mChildren.ChildCount();
   for (i = 0; i < count; ++i) {
     mChildren.ChildAt(i)->SaveSubtreeState();
   }
 }
 
 already_AddRefed<nsILayoutHistoryState>
@@ -7151,16 +7172,18 @@ nsDocument::DispatchPageTransition(nsPID
     }
   }
 }
 
 void
 nsDocument::OnPageShow(PRBool aPersisted, nsIDOMEventTarget* aDispatchStartTarget)
 {
   mVisible = PR_TRUE;
+
+  EnumerateFreezableElements(NotifyActivityChanged, nsnull); 
   UpdateLinkMap();
   
   nsIContent* root = GetRootContent();
   if (aPersisted && root) {
     // Send out notifications that our <link> elements are attached.
     nsRefPtr<nsContentList> links = NS_GetContentList(root,
                                                       nsGkAtoms::link,
                                                       kNameSpaceID_Unknown);
@@ -7231,16 +7254,17 @@ nsDocument::OnPageHide(PRBool aPersisted
   
   // Now send out a PageHide event.
   nsCOMPtr<nsPIDOMEventTarget> target =
     aDispatchStartTarget ? do_QueryInterface(aDispatchStartTarget) :
                            do_QueryInterface(GetWindow());
   DispatchPageTransition(target, NS_LITERAL_STRING("pagehide"), aPersisted);
 
   mVisible = PR_FALSE;
+  EnumerateFreezableElements(NotifyActivityChanged, nsnull);
 }
 
 void
 nsDocument::WillDispatchMutationEvent(nsINode* aTarget)
 {
   NS_ASSERTION(mSubtreeModifiedDepth != 0 ||
                mSubtreeModifiedTargets.Count() == 0,
                "mSubtreeModifiedTargets not cleared after dispatching?");
--- a/content/base/src/nsNodeUtils.cpp
+++ b/content/base/src/nsNodeUtils.cpp
@@ -51,16 +51,17 @@
 #include "nsCOMArray.h"
 #include "nsPIDOMWindow.h"
 #include "nsDocument.h"
 #ifdef MOZ_XUL
 #include "nsXULElement.h"
 #endif
 #include "nsBindingManager.h"
 #include "nsGenericHTMLElement.h"
+#include "nsHTMLMediaElement.h"
 
 // This macro expects the ownerDocument of content_ to be in scope as
 // |nsIDocument* doc|
 #define IMPL_MUTATION_NOTIFICATION(func_, content_, params_)      \
   PR_BEGIN_MACRO                                                  \
   nsINode* node = content_;                                       \
   NS_ASSERTION(node->GetOwnerDoc() == doc, "Bogus document");     \
   if (doc) {                                                      \
@@ -599,32 +600,44 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNod
       oldDoc->ClearBoxObjectFor(content);
       wasRegistered = oldDoc->UnregisterFreezableElement(content);
     }
 
     aNode->mNodeInfo.swap(newNodeInfo);
 
     nsIDocument* newDoc = aNode->GetOwnerDoc();
     if (newDoc) {
+      // XXX what if oldDoc is null, we don't know if this should be
+      // registered or not! Can that really happen?
       if (wasRegistered) {
         newDoc->RegisterFreezableElement(static_cast<nsIContent*>(aNode));
       }
 
       nsPIDOMWindow* window = newDoc->GetInnerWindow();
       if (window) {
         nsIEventListenerManager* elm = aNode->GetListenerManager(PR_FALSE);
         if (elm) {
           window->SetMutationListeners(elm->MutationListenerBits());
           if (elm->MayHavePaintEventListener()) {
             window->SetHasPaintEventListeners();
           }
         }
       }
     }
 
+#ifdef MOZ_MEDIA
+    if (wasRegistered && oldDoc != newDoc) {
+      nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aNode));
+      if (domMediaElem) {
+        nsHTMLMediaElement* mediaElem = static_cast<nsHTMLMediaElement*>(aNode);
+        mediaElem->NotifyOwnerDocumentActivityChanged();
+      }
+    }
+#endif
+
     if (elem) {
       elem->RecompileScriptEventListeners();
     }
 
     if (aCx) {
       nsIXPConnect *xpc = nsContentUtils::XPConnect();
       if (xpc) {
         nsCOMPtr<nsIXPConnectJSObjectHolder> oldWrapper;
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -83,26 +83,33 @@ public:
   nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                    const nsAString& aValue, PRBool aNotify)
   {
     return SetAttr(aNameSpaceID, aName, nsnull, aValue, aNotify);
   }
   virtual nsresult SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                            nsIAtom* aPrefix, const nsAString& aValue,
                            PRBool aNotify);
+  virtual nsresult UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttr, 
+                             PRBool aNotify);
 
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                               nsIContent* aBindingParent,
                               PRBool aCompileEventHandlers);
   virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                               PRBool aNullParent = PR_TRUE);
 
   virtual PRBool IsDoneAddingChildren();
   virtual nsresult DoneAddingChildren(PRBool aHaveNotified);
-  virtual void DestroyContent();
+
+  /**
+   * Call this to reevaluate whether we should start/stop due to our owner
+   * document being active or inactive.
+   */
+  void NotifyOwnerDocumentActivityChanged();
 
   // Called by the video decoder object, on the main thread,
   // when it has read the metadata containing video dimensions,
   // etc.
   void MetadataLoaded();
 
   // Called by the video decoder object, on the main thread,
   // when it has read the first frame of the video
@@ -175,16 +182,19 @@ public:
     NEXT_FRAME_UNAVAILABLE
   };
   void UpdateReadyStateForData(NextFrameStatus aNextFrame);
 
   // Use this method to change the mReadyState member, so required
   // events can be fired.
   void ChangeReadyState(nsMediaReadyState aState);
 
+  // Return true if we can activate autoplay assuming enough data has arrived.
+  PRBool CanActivateAutoplay();
+
   // Notify that enough data has arrived to start autoplaying.
   // If the element is 'autoplay' and is ready to play back (not paused,
   // autoplay pref enabled, etc), it should start playing back.
   void NotifyAutoplayDataReady();
 
   // Gets the pref media.enforce_same_site_origin, which determines
   // if we should check Access Controls, or allow cross domain loads.
   PRBool ShouldCheckAllowOrigin();
@@ -199,21 +209,16 @@ public:
 
   // principal of the currently playing stream
   already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
 
   // Update the visual size of the media. Called from the decoder on the
   // main thread when/if the size changes.
   void UpdateMediaSize(nsIntSize size);
 
-  // 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.
   // If it returns true, then it also returns a null-terminated list
   // of supported codecs in *aSupportedCodecs. This
   // list should not be freed, it is static data.
   static PRBool CanHandleMediaType(const char* aMIMEType,
                                    const char*** aSupportedCodecs);
 
   // Returns true if we should handle this MIME type when it appears
@@ -272,20 +277,20 @@ protected:
   /**
    * Changes mHasPlayedOrSeeked to aValue. If mHasPlayedOrSeeked changes
    * we'll force a reflow so that the video frame gets reflowed to reflect
    * the poster hiding or showing immediately.
    */
   void SetPlayedOrSeeked(PRBool aValue);
 
   /**
-   * Create a decoder for the given aMIMEType. Returns false if we
+   * Create a decoder for the given aMIMEType. Returns null if we
    * were unable to create the decoder.
    */
-  PRBool CreateDecoder(const nsACString& aMIMEType);
+  already_AddRefed<nsMediaDecoder> CreateDecoder(const nsACString& aMIMEType);
 
   /**
    * Initialize a decoder as a clone of an existing decoder in another
    * element.
    */
   nsresult InitializeDecoderAsClone(nsMediaDecoder* aOriginal); 
 
   /**
@@ -293,17 +298,17 @@ protected:
    * listener is returned via aListener.
    */
   nsresult InitializeDecoderForChannel(nsIChannel *aChannel,
                                        nsIStreamListener **aListener);
 
   /**
    * Finish setting up the decoder after Load() has been called on it.
    */
-  nsresult FinishDecoderSetup();
+  nsresult FinishDecoderSetup(nsMediaDecoder* aDecoder);
 
   /**
    * Execute the initial steps of the load algorithm that ensure existing
    * loads are aborted, the element is emptied, and a new load ID is
    * created.
    */
   void AbortExistingLoads();
 
@@ -366,16 +371,28 @@ protected:
   /**
    * Called when our channel is redirected to another channel.
    * Updates our mChannel reference to aNewChannel.
    */
   nsresult OnChannelRedirect(nsIChannel *aChannel,
                              nsIChannel *aNewChannel,
                              PRUint32 aFlags);
 
+  /**
+   * Call this to reevaluate whether we should be holding a self-reference.
+   */
+  void AddRemoveSelfReference();
+
+  /**
+   * Alias for Release(), but using stdcall calling convention so on
+   * platforms where Release has a strange calling convention (Windows)
+   * we can still get a method pointer to this method.
+   */
+  void DoRelease() { Release(); }
+
   nsRefPtr<nsMediaDecoder> mDecoder;
 
   // Holds a reference to the first channel we open to the media resource.
   // Once the decoder is created, control over the channel passes to the
   // decoder, and we null out this reference. We must store this in case
   // we need to cancel the channel before control of it passes to the decoder.
   nsCOMPtr<nsIChannel> mChannel;
 
@@ -457,20 +474,18 @@ protected:
   PRPackedBool mIsDoneAddingChildren;
 
   // If TRUE then the media element was actively playing before the currently
   // in progress seeking. If FALSE then the media element is either not seeking
   // or was not actively playing before the current seek. Used to decide whether
   // to raise the 'waiting' event as per 4.7.1.8 in HTML 5 specification.
   PRPackedBool mPlayingBeforeSeek;
 
-  // PR_TRUE if the video was paused before Freeze was called. This is checked
-  // to ensure that the playstate doesn't change when the user goes Forward/Back
-  // from the bfcache.
-  PRPackedBool mPausedBeforeFreeze;
+  // PR_TRUE iff this element is paused because the document is inactive
+  PRPackedBool mPausedForInactiveDocument;
   
   // PR_TRUE if we've reported a "waiting" event since the last
   // readyState change to HAVE_CURRENT_DATA.
   PRPackedBool mWaitingFired;
 
   // PR_TRUE if we're in BindToTree().
   PRPackedBool mIsBindingToTree;
 
@@ -494,9 +509,14 @@ protected:
 
   // PR_TRUE if we are allowed to suspend the decoder because we were paused,
   // autobuffer and autoplay were not set, and we loaded the first frame.
   PRPackedBool mAllowSuspendAfterFirstFrame;
 
   // PR_TRUE if we've played or completed a seek. We use this to determine
   // when the poster frame should be shown.
   PRPackedBool mHasPlayedOrSeeked;
+
+  // PR_TRUE if we've added a reference to ourselves to keep the element
+  // alive while no-one is referencing it but the element may still fire
+  // events of its own accord.
+  PRPackedBool mHasSelfReference;
 };
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -91,16 +91,58 @@ static PRLogModuleInfo* gMediaElementLog
 static PRLogModuleInfo* gMediaElementEventsLog;
 #define LOG(type, msg) PR_LOG(gMediaElementLog, type, msg)
 #define LOG_EVENT(type, msg) PR_LOG(gMediaElementEventsLog, type, msg)
 #else
 #define LOG(type, msg)
 #define LOG_EVENT(type, msg)
 #endif
 
+// Under certain conditions there may be no-one holding references to
+// a media element from script, DOM parent, etc, but the element may still
+// fire meaningful events in the future so we can't destroy it yet:
+// 1) If the element is delaying the load event (or would be, if it were
+// in a document), then events up to loadeddata or error could be fired,
+// so we need to stay alive.
+// 2) If the element is not paused and playback has not ended, then
+// we will (or might) play, sending timeupdate and ended events and possibly
+// audio output, so we need to stay alive.
+// 3) if the element is seeking then we will fire seeking events and possibly
+// start playing afterward, so we need to stay alive.
+// 4) If autoplay could start playback in this element (if we got enough data),
+// then we need to stay alive.
+// 5) if the element is currently loading and not suspended,
+// script might be waiting for progress events or a 'suspend' event,
+// so we need to stay alive. If we're already suspended then (all other
+// conditions being met) it's OK to just disappear without firing any more
+// events, since we have the freedom to remain suspended indefinitely. Note
+// that we could use this 'suspended' loophole to garbage-collect a suspended
+// element in case 4 even if it had 'autoplay' set, but we choose not to.
+// If someone throws away all references to a loading 'autoplay' element
+// sound should still eventually play.
+//
+// Media elements owned by inactive documents (i.e. documents not contained in any
+// document viewer) should never hold a self-reference because none of the
+// above conditions are allowed: the element will stop loading and playing
+// and never resume loading or playing unless its owner document changes to
+// an active document (which can only happen if there is an external reference
+// to the element).
+// Media elements with no owner doc should be able to hold a self-reference.
+// Something native must have created the element and may expect it to
+// stay alive to play.
+
+// It's very important that any change in state which could change the value of
+// needSelfReference in AddRemoveSelfReference be followed by a call to
+// AddRemoveSelfReference before this element could die!
+// It's especially important if needSelfReference would change to 'true',
+// since if we neglect to add a self-reference, this element might be
+// garbage collected while there are still event listeners that should
+// receive events. If we neglect to remove the self-reference then the element
+// just lives longer than it needs to.
+
 class nsMediaEvent : public nsRunnable
 {
 public:
 
   nsMediaEvent(nsHTMLMediaElement* aElement) :
     mElement(aElement),
     mLoadID(mElement->GetCurrentLoadID()) {}
   ~nsMediaEvent() {}
@@ -409,24 +451,31 @@ void nsHTMLMediaElement::AbortExistingLo
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
     mPaused = PR_TRUE;
 
     // TODO: The current playback position must be set to 0.
     DispatchSimpleEvent(NS_LITERAL_STRING("emptied"));
   }
 
+  // We may have changed mPaused, mAutoplaying, mNetworkState and other
+  // things which can affect AddRemoveSelfReference
+  AddRemoveSelfReference();
+
   mIsRunningSelectResource = PR_FALSE;
 }
 
 void nsHTMLMediaElement::NoSupportedMediaSourceError()
 {
+  NS_ASSERTION(mDelayingLoadEvent, "Load event not delayed during source selection?");
+
   mError = new nsHTMLMediaError(nsIDOMHTMLMediaError::MEDIA_ERR_SRC_NOT_SUPPORTED);
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE;
   DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
+  // This clears mDelayingLoadEvent, so AddRemoveSelfReference will be called
   ChangeDelayLoadStatus(PR_FALSE);
 }
 
 /* void load (); */
 NS_IMETHODIMP nsHTMLMediaElement::Load()
 {
   if (mIsRunningLoadMethod)
     return NS_OK;
@@ -460,26 +509,31 @@ static PRBool HasPotentialResource(nsICo
   nsAutoString src;
   if (aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src))
     return PR_TRUE;
   return HasSourceChildren(aElement);
 }
 
 void nsHTMLMediaElement::SelectResource()
 {
+  NS_ASSERTION(mDelayingLoadEvent, "Load event not delayed during resource selection?");
+
   if (!HasPotentialResource(this)) {
     // While the media element has neither a src attribute nor any source
     // element children, wait. (This steps might wait forever.) 
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_NO_SOURCE;
     mLoadWaitStatus = WAITING_FOR_SRC_OR_SOURCE;
+    // This clears mDelayingLoadEvent, so AddRemoveSelfReference will be called
     ChangeDelayLoadStatus(PR_FALSE);
     return;
   }
 
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
+  // Load event was delayed, and still is, so no need to call
+  // AddRemoveSelfReference, since it must still be held
   DispatchAsyncProgressEvent(NS_LITERAL_STRING("loadstart"));
 
   nsAutoString src;
   nsCOMPtr<nsIURI> uri;
 
   // If we have a 'src' attribute, use that exclusively.
   if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
     nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
@@ -503,18 +557,18 @@ void nsHTMLMediaElement::NotifyLoadError
     NoSupportedMediaSourceError();
   } else {
     QueueLoadFromSourceTask();
   }
 }
 
 void nsHTMLMediaElement::LoadFromSourceChildren()
 {
-  NS_ASSERTION(!IsInDoc() || mDelayingLoadEvent,
-               "Should delay load event while loading in document");
+  NS_ASSERTION(mDelayingLoadEvent,
+               "Should delay load event (if in document) during load");
   while (PR_TRUE) {
     nsresult rv;
     nsCOMPtr<nsIURI> uri = GetNextSource();
     if (!uri) {
       // Exhausted candidates, wait for more candidates to be appended to
       // the media element.
       mLoadWaitStatus = WAITING_FOR_SOURCE;
       NoSupportedMediaSourceError();
@@ -529,18 +583,18 @@ void nsHTMLMediaElement::LoadFromSourceC
 
     // If we fail to load, loop back and try loading the next resource.
   }
   NS_NOTREACHED("Execution should not reach here!");
 }
 
 nsresult nsHTMLMediaElement::LoadResource(nsIURI* aURI)
 {
-  NS_ASSERTION(!IsInDoc() || mDelayingLoadEvent,
-               "Should delay load event while loading in document");
+  NS_ASSERTION(mDelayingLoadEvent,
+               "Should delay load event (if in document) during load");
   nsresult rv;
 
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannel = nsnull;
   }
 
   PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
@@ -716,16 +770,20 @@ NS_IMETHODIMP nsHTMLMediaElement::SetCur
     clampedTime = PR_MIN(clampedTime, duration);
   }
 
   mPlayingBeforeSeek = IsPotentiallyPlaying();
   // The media backend is responsible for dispatching the timeupdate
   // event if it changes the playback position as a result of the seek.
   LOG(PR_LOG_DEBUG, ("%p SetCurrentTime(%f) starting seek", this, aCurrentTime));  
   nsresult rv = mDecoder->Seek(clampedTime);
+
+  // We changed whether we're seeking so we need to AddRemoveSelfReference
+  AddRemoveSelfReference();
+
   return rv;
 }
 
 /* readonly attribute float duration; */
 NS_IMETHODIMP nsHTMLMediaElement::GetDuration(float *aDuration)
 {
   *aDuration =  mDecoder ? mDecoder->GetDuration() : 0.0;
   return NS_OK;
@@ -747,16 +805,18 @@ NS_IMETHODIMP nsHTMLMediaElement::Pause(
     NS_ENSURE_SUCCESS(rv, rv);
   } else if (mDecoder) {
     mDecoder->Pause();
   }
 
   PRBool oldPaused = mPaused;
   mPaused = PR_TRUE;
   mAutoplaying = PR_FALSE;
+  // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
+  AddRemoveSelfReference();
   
   if (!oldPaused) {
     DispatchAsyncSimpleEvent(NS_LITERAL_STRING("timeupdate"));
     DispatchAsyncSimpleEvent(NS_LITERAL_STRING("pause"));
   }
 
   return NS_OK;
 }
@@ -822,41 +882,46 @@ nsHTMLMediaElement::nsHTMLMediaElement(n
     mBegun(PR_FALSE),
     mLoadedFirstFrame(PR_FALSE),
     mAutoplaying(PR_TRUE),
     mAutoplayEnabled(PR_TRUE),
     mPaused(PR_TRUE),
     mMuted(PR_FALSE),
     mIsDoneAddingChildren(!aFromParser),
     mPlayingBeforeSeek(PR_FALSE),
-    mPausedBeforeFreeze(PR_FALSE),
+    mPausedForInactiveDocument(PR_FALSE),
     mWaitingFired(PR_FALSE),
     mIsBindingToTree(PR_FALSE),
     mIsRunningLoadMethod(PR_FALSE),
     mIsLoadingFromSrcAttribute(PR_FALSE),
     mDelayingLoadEvent(PR_FALSE),
     mIsRunningSelectResource(PR_FALSE),
     mSuspendedAfterFirstFrame(PR_FALSE),
     mAllowSuspendAfterFirstFrame(PR_TRUE),
-    mHasPlayedOrSeeked(PR_FALSE)
+    mHasPlayedOrSeeked(PR_FALSE),
+    mHasSelfReference(PR_FALSE)
 {
 #ifdef PR_LOGGING
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
   }
 #endif
 
   RegisterFreezableElement();
+  NotifyOwnerDocumentActivityChanged();
 }
 
 nsHTMLMediaElement::~nsHTMLMediaElement()
 {
+  NS_ASSERTION(!mHasSelfReference,
+               "How can we be destroyed if we're still holding a self reference?");
+
   UnregisterFreezableElement();
   if (mDecoder) {
     mDecoder->Shutdown();
     mDecoder = nsnull;
   }
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
     mChannel = nsnull;
@@ -900,18 +965,20 @@ NS_IMETHODIMP nsHTMLMediaElement::Play()
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
     nsresult rv = Load();
     NS_ENSURE_SUCCESS(rv, rv);
   } else if (mDecoder) {
     if (mDecoder->IsEnded()) {
       SetCurrentTime(0);
     }
-    nsresult rv = mDecoder->Play();
-    NS_ENSURE_SUCCESS(rv, rv);
+    if (!mPausedForInactiveDocument) {
+      nsresult rv = mDecoder->Play();
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
   }
 
   // TODO: If the playback has ended, then the user agent must set 
   // seek to the effective start.
   // TODO: The playback rate must be set to the default playback rate.
   if (mPaused) {
     DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
     switch (mReadyState) {
@@ -923,16 +990,18 @@ NS_IMETHODIMP nsHTMLMediaElement::Play()
     case nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA:
       DispatchAsyncSimpleEvent(NS_LITERAL_STRING("playing"));
       break;
     }
   }
 
   mPaused = PR_FALSE;
   mAutoplaying = PR_FALSE;
+  // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
+  AddRemoveSelfReference();
 
   return NS_OK;
 }
 
 PRBool nsHTMLMediaElement::ParseAttribute(PRInt32 aNamespaceID,
                                           nsIAtom* aAttribute,
                                           const nsAString& aValue,
                                           nsAttrValue& aResult)
@@ -974,24 +1043,42 @@ nsresult nsHTMLMediaElement::SetAttr(PRI
         mLoadWaitStatus = NOT_WAITING;
         QueueSelectResourceTask();
       }
     } else if (aName == nsGkAtoms::autoplay) {
       StopSuspendingAfterFirstFrame();
       if (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
         NotifyAutoplayDataReady();
       }
+      // This attribute can affect AddRemoveSelfReference
+      AddRemoveSelfReference();
     } else if (aName == nsGkAtoms::autobuffer) {
       StopSuspendingAfterFirstFrame();
     }
   }
 
   return rv;
 }
 
+nsresult nsHTMLMediaElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttr, 
+                                       PRBool aNotify)
+{
+  nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttr, aNotify);
+  if (aNotify && aNameSpaceID == kNameSpaceID_None) {
+    if (aAttr == nsGkAtoms::autoplay) {
+      // This attribute can affect AddRemoveSelfReference
+      AddRemoveSelfReference();
+    }
+    // We perhaps should stop loading if 'autobuffer' is being removed,
+    // and we're buffering only because of 'autobuffer', but why bother?
+  }
+
+  return rv;
+}
+
 static PRBool IsAutoplayEnabled()
 {
   return nsContentUtils::GetBoolPref("media.autoplay.enabled");
 }
 
 nsresult nsHTMLMediaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                         nsIContent* aBindingParent,
                                         PRBool aCompileEventHandlers)
@@ -1222,115 +1309,124 @@ void nsHTMLMediaElement::ShutdownMediaTy
 #ifdef MOZ_WAVE
     for (PRUint32 i = 0; i < NS_ARRAY_LENGTH(gWaveTypes); i++) {
       catMan->DeleteCategoryEntry("Gecko-Content-Viewers", gWaveTypes[i], PR_FALSE);
     }
 #endif
   }
 }
 
-PRBool nsHTMLMediaElement::CreateDecoder(const nsACString& aType)
+already_AddRefed<nsMediaDecoder>
+nsHTMLMediaElement::CreateDecoder(const nsACString& aType)
 {
 #ifdef MOZ_OGG
   if (IsOggType(aType)) {
-    mDecoder = new nsOggDecoder();
-    if (mDecoder && !mDecoder->Init(this)) {
-      mDecoder = nsnull;
+    nsRefPtr<nsOggDecoder> decoder = new nsOggDecoder();
+    if (decoder && decoder->Init(this)) {
+      return decoder.forget().get();
     }
   }
 #endif
 #ifdef MOZ_WAVE
   if (IsWaveType(aType)) {
-    mDecoder = new nsWaveDecoder();
-    if (mDecoder && !mDecoder->Init(this)) {
-      mDecoder = nsnull;
+    nsRefPtr<nsWaveDecoder> decoder = new nsWaveDecoder();
+    if (decoder && decoder->Init(this)) {
+      return decoder.forget().get();
     }
   }
 #endif
-  return mDecoder != nsnull;
+  return nsnull;
 }
 
 nsresult nsHTMLMediaElement::InitializeDecoderAsClone(nsMediaDecoder* aOriginal)
 {
   nsMediaStream* originalStream = aOriginal->GetCurrentStream();
   if (!originalStream)
     return NS_ERROR_FAILURE;
-  mDecoder = aOriginal->Clone();
-  if (!mDecoder)
+  nsRefPtr<nsMediaDecoder> decoder = aOriginal->Clone();
+  if (!decoder)
     return NS_ERROR_FAILURE;
 
-  LOG(PR_LOG_DEBUG, ("%p Cloned decoder %p from %p", this, mDecoder.get(), aOriginal));  
+  LOG(PR_LOG_DEBUG, ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal));  
 
-  if (!mDecoder->Init(this)) {
-    mDecoder = nsnull;
+  if (!decoder->Init(this)) {
     return NS_ERROR_FAILURE;
   }
 
   float duration = aOriginal->GetDuration();
   if (duration >= 0) {
     mDecoder->SetDuration(PRInt64(NS_round(duration * 1000)));
     mDecoder->SetSeekable(aOriginal->GetSeekable());
   }
 
-  nsMediaStream* stream = originalStream->CloneData(mDecoder);
+  nsMediaStream* stream = originalStream->CloneData(decoder);
   if (!stream) {
-    mDecoder = nsnull;
     return NS_ERROR_FAILURE;
   }
 
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
 
-  nsresult rv = mDecoder->Load(stream, nsnull);
+  nsresult rv = decoder->Load(stream, nsnull);
   if (NS_FAILED(rv)) {
-    mDecoder = nsnull;
     return rv;
   }
 
-  return FinishDecoderSetup();
+  return FinishDecoderSetup(decoder);
 }
 
 nsresult nsHTMLMediaElement::InitializeDecoderForChannel(nsIChannel *aChannel,
                                                          nsIStreamListener **aListener)
 {
   nsCAutoString mimeType;
   aChannel->GetContentType(mimeType);
 
-  if (!CreateDecoder(mimeType))
+  nsRefPtr<nsMediaDecoder> decoder = CreateDecoder(mimeType);
+  if (!decoder) {
     return NS_ERROR_FAILURE;
+  }
 
-  LOG(PR_LOG_DEBUG, ("%p Created decoder %p for type %s", this, mDecoder.get(), mimeType.get()));  
+  LOG(PR_LOG_DEBUG, ("%p Created decoder %p for type %s", this, decoder.get(), mimeType.get()));  
 
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
 
-  nsMediaStream* stream = nsMediaStream::Create(mDecoder, aChannel);
+  nsMediaStream* stream = nsMediaStream::Create(decoder, aChannel);
   if (!stream)
     return NS_ERROR_OUT_OF_MEMORY;
 
-  nsresult rv = mDecoder->Load(stream, aListener);
+  nsresult rv = decoder->Load(stream, aListener);
   if (NS_FAILED(rv)) {
-    mDecoder = nsnull;
     return rv;
   }
 
   // Decoder successfully created, the decoder now owns the nsMediaStream
   // which owns the channel.
   mChannel = nsnull;
 
-  return FinishDecoderSetup();
+  return FinishDecoderSetup(decoder);
 }
 
-nsresult nsHTMLMediaElement::FinishDecoderSetup()
+nsresult nsHTMLMediaElement::FinishDecoderSetup(nsMediaDecoder* aDecoder)
 {
+  mDecoder = aDecoder;
+
+  // The new stream has not been suspended by us.
+  mPausedForInactiveDocument = PR_FALSE;
+  // But we may want to suspend it now.
+  // This will also do an AddRemoveSelfReference.
+  NotifyOwnerDocumentActivityChanged();
+
   nsresult rv = NS_OK;
 
   mDecoder->SetVolume(mMuted ? 0.0 : mVolume);
 
   if (!mPaused) {
     SetPlayedOrSeeked(PR_TRUE);
-    rv = mDecoder->Play();
+    if (!mPausedForInactiveDocument) {
+      rv = mDecoder->Play();
+    }
   }
 
   mBegun = PR_TRUE;
   return rv;
 }
 
 nsresult nsHTMLMediaElement::NewURIFromString(const nsAutoString& aURISpec, nsIURI** aURI)
 {
@@ -1388,71 +1484,81 @@ void nsHTMLMediaElement::FirstFrameLoade
     mDecoder->Suspend();
   }
 }
 
 void nsHTMLMediaElement::ResourceLoaded()
 {
   mBegun = PR_FALSE;
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
+  AddRemoveSelfReference();
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA);
   // The download has stopped
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("suspend"));
 }
 
 void nsHTMLMediaElement::NetworkError()
 {
   mError = new nsHTMLMediaError(nsIDOMHTMLMediaError::MEDIA_ERR_NETWORK);
   mBegun = PR_FALSE;
   DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
+  AddRemoveSelfReference();
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("emptied"));
   ChangeDelayLoadStatus(PR_FALSE);
 }
 
 void nsHTMLMediaElement::DecodeError()
 {
   mError = new nsHTMLMediaError(nsIDOMHTMLMediaError::MEDIA_ERR_DECODE);
   mBegun = PR_FALSE;
   DispatchAsyncProgressEvent(NS_LITERAL_STRING("error"));
   mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
+  AddRemoveSelfReference();
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("emptied"));
   ChangeDelayLoadStatus(PR_FALSE);
 }
 
 void nsHTMLMediaElement::PlaybackEnded()
 {
   NS_ASSERTION(mDecoder->IsEnded(), "Decoder fired ended, but not in ended state");
+  // We changed the state of IsPlaybackEnded which can affect AddRemoveSelfReference
+  AddRemoveSelfReference();
+
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("ended"));
 }
 
 void nsHTMLMediaElement::SeekStarted()
 {
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeking"));
 }
 
 void nsHTMLMediaElement::SeekCompleted()
 {
   mPlayingBeforeSeek = PR_FALSE;
   SetPlayedOrSeeked(PR_TRUE);
   DispatchAsyncSimpleEvent(NS_LITERAL_STRING("seeked"));
+  // We changed whether we're seeking so we need to AddRemoveSelfReference
+  AddRemoveSelfReference();
 }
 
 void nsHTMLMediaElement::DownloadSuspended()
 {
   if (mBegun) {
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
+    AddRemoveSelfReference();
     DispatchAsyncSimpleEvent(NS_LITERAL_STRING("suspend"));
   }
 }
 
 void nsHTMLMediaElement::DownloadResumed()
 {
   if (mBegun) {
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
+    AddRemoveSelfReference();
   }
 }
 
 void nsHTMLMediaElement::DownloadStalled()
 {
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING) {
     DispatchAsyncProgressEvent(NS_LITERAL_STRING("stalled"));
   }
@@ -1573,23 +1679,31 @@ void nsHTMLMediaElement::ChangeReadyStat
   }
 
   if (oldState < nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA &&
       mReadyState >= nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA) {
     DispatchAsyncSimpleEvent(NS_LITERAL_STRING("canplaythrough"));
   }
 }
 
+PRBool nsHTMLMediaElement::CanActivateAutoplay()
+{
+  return mAutoplaying &&
+         mPaused &&
+         HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
+         mAutoplayEnabled;
+}
+
 void nsHTMLMediaElement::NotifyAutoplayDataReady()
 {
-  if (mAutoplaying &&
-      mPaused &&
-      HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
-      mAutoplayEnabled) {
+  if (CanActivateAutoplay()) {
     mPaused = PR_FALSE;
+    // We changed mPaused which can affect AddRemoveSelfReference
+    AddRemoveSelfReference();
+
     if (mDecoder) {
       SetPlayedOrSeeked(PR_TRUE);
       mDecoder->Play();
     }
     DispatchAsyncSimpleEvent(NS_LITERAL_STRING("play"));
   }
 }
 
@@ -1680,17 +1794,17 @@ PRBool nsHTMLMediaElement::IsDoneAddingC
   return mIsDoneAddingChildren;
 }
 
 PRBool nsHTMLMediaElement::IsPotentiallyPlaying() const
 {
   // TODO: 
   //   playback has not stopped due to errors, 
   //   and the element has not paused for user interaction
-  return 
+  return
     !mPaused && 
     (mReadyState == nsIDOMHTMLMediaElement::HAVE_ENOUGH_DATA ||
     mReadyState == nsIDOMHTMLMediaElement::HAVE_FUTURE_DATA) &&
     !IsPlaybackEnded();
 }
 
 PRBool nsHTMLMediaElement::IsPlaybackEnded() const
 {
@@ -1709,48 +1823,71 @@ already_AddRefed<nsIPrincipal> nsHTMLMed
   return mDecoder->GetCurrentPrincipal();
 }
 
 void nsHTMLMediaElement::UpdateMediaSize(nsIntSize size)
 {
   mMediaSize = size;
 }
 
-void nsHTMLMediaElement::DestroyContent()
+void nsHTMLMediaElement::NotifyOwnerDocumentActivityChanged()
 {
-  if (mDecoder) {
-    mDecoder->Shutdown();
-    mDecoder = nsnull;
+  nsIDocument* ownerDoc = GetOwnerDoc();
+  // Don't pause if we have no ownerDoc. Something native must have created
+  // us and be expecting us to work without a document.
+  PRBool pauseForInactiveDocument =
+    ownerDoc && (!ownerDoc->IsActive() || !ownerDoc->IsVisible());
+
+  if (pauseForInactiveDocument != mPausedForInactiveDocument) {
+    mPausedForInactiveDocument = pauseForInactiveDocument;
+    if (mDecoder) {
+      if (pauseForInactiveDocument) {
+        mDecoder->Pause();
+        mDecoder->Suspend();
+      } else {
+        mDecoder->Resume();
+        if (IsPotentiallyPlaying()) {
+          mDecoder->Play();
+        }
+      }
+    }
   }
-  if (mChannel) {
-    mChannel->Cancel(NS_BINDING_ABORTED);
-    mChannel = nsnull;
-  }
-  nsGenericHTMLElement::DestroyContent();
+
+  AddRemoveSelfReference();
 }
 
-void nsHTMLMediaElement::Freeze()
+void nsHTMLMediaElement::AddRemoveSelfReference()
 {
-  mPausedBeforeFreeze = mPaused;
-  if (!mPaused) {
-    Pause();
-  }
-  if (mDecoder) {
-    mDecoder->Suspend();
-  }
-}
+  // XXX we could release earlier here in many situations if we examined
+  // which event listeners are attached. Right now we assume there is a
+  // potential listener for every event. We would also have to keep the
+  // element alive if it was playing and producing audio output --- right now
+  // that's covered by the !mPaused check.
+  nsIDocument* ownerDoc = GetOwnerDoc();
 
-void nsHTMLMediaElement::Thaw()
-{
-  if (!mPausedBeforeFreeze) {
-    Play();
-  }
+  // See the comment at the top of this file for the explanation of this
+  // boolean expression.
+  PRBool needSelfReference = (!ownerDoc || ownerDoc->IsActive()) &&
+    (mDelayingLoadEvent ||
+     (!mPaused && mDecoder && !mDecoder->IsEnded()) ||
+     (mDecoder && mDecoder->IsSeeking()) ||
+     CanActivateAutoplay() ||
+     mNetworkState == nsIDOMHTMLMediaElement::NETWORK_LOADING);
 
-  if (mDecoder) {
-    mDecoder->Resume();
+  if (needSelfReference != mHasSelfReference) {
+    mHasSelfReference = needSelfReference;
+    if (needSelfReference) {
+      NS_ADDREF(this);
+    } else {
+      // Dispatch Release asynchronously so that we don't destroy this object
+      // inside a call stack of method calls on this object
+      nsCOMPtr<nsIRunnable> event =
+        NS_NEW_RUNNABLE_METHOD(nsHTMLMediaElement, this, DoRelease);
+      NS_DispatchToMainThread(event);
+    }
   }
 }
 
 PRBool
 nsHTMLMediaElement::IsNodeOfType(PRUint32 aFlags) const
 {
   return !(aFlags & ~(eCONTENT | eELEMENT | eMEDIA));
 }
@@ -1843,15 +1980,18 @@ void nsHTMLMediaElement::ChangeDelayLoad
     if (mDecoder) {
       mDecoder->MoveLoadsToBackground();
     }
     NS_ASSERTION(mLoadBlockedDoc, "Need a doc to block on");
     LOG(PR_LOG_DEBUG, ("%p ChangeDelayLoadStatus(%d) doc=0x%p", this, aDelay, mLoadBlockedDoc.get()));
     mLoadBlockedDoc->UnblockOnload(PR_FALSE);
     mLoadBlockedDoc = nsnull;
   }
+
+  // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference
+  AddRemoveSelfReference();
 }
 
 already_AddRefed<nsILoadGroup> nsHTMLMediaElement::GetDocumentLoadGroup()
 {
   nsIDocument* doc = GetOwnerDoc();
   return doc ? doc->GetDocumentLoadGroup() : nsnull;
 }
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -246,19 +246,19 @@ protected:
                   float aFramerate,
                   float aAspectRatio,
                   unsigned char* aRGBBuffer);
 
 protected:
   // Timer used for updating progress events 
   nsCOMPtr<nsITimer> mProgressTimer;
 
-  // The element is not reference counted. Instead the decoder is
-  // notified when it is able to be used. It should only ever be
-  // accessed from the main thread.
+  // This should only ever be accessed from the main thread.
+  // It is set in Init and cleared in Shutdown when the element goes away.
+  // The decoder does not add a reference the element.
   nsHTMLMediaElement* mElement;
 
   // RGB data for last decoded frame of video data.
   // The size of the buffer is mRGBWidth*mRGBHeight*4 bytes and
   // contains bytes in RGBA format.
   nsAutoArrayPtr<unsigned char> mRGB;
 
   PRInt32 mRGBWidth;
--- a/content/media/test/Makefile.in
+++ b/content/media/test/Makefile.in
@@ -66,16 +66,17 @@ include $(topsrcdir)/config/rules.mk
 # make the test first check canPlayType for the type, and if it's not
 # supported, just do ok(true, "Type not supported") and stop the test.
 
 _TEST_FILES = \
 		can_play_type_ogg.js \
 		can_play_type_wave.js \
 		cancellable_request.sjs \
 		manifest.js \
+		reactivate_helper.html \
 		seek1.js \
 		seek2.js \
 		seek3.js \
 		seek4.js \
 		seek5.js \
 		seek6.js \
 		seek7.js \
 		seek8.js \
@@ -94,16 +95,17 @@ include $(topsrcdir)/config/rules.mk
 		test_decoder_disable.html \
 		test_load.html \
 		test_media_selection.html \
 		test_mozLoadFrom.html \
 		test_networkState.html \
 		test_paused.html \
 		test_playback.html \
 		test_playback_errors.html \
+		test_reactivate.html \
 		test_readyState.html \
 		test_seek2.html \
 		test_volume.html \
 		use_large_cache.js \
 		$(NULL)
 
 # Disabled due to flakiness
 #		test_resume.html \
new file mode 100644
--- /dev/null
+++ b/content/media/test/reactivate_helper.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<script>
+var loadsWaiting = 0;
+var elements = [];
+
+function loadedData(event) {
+  parent.ok(elements.indexOf(event.target) == -1, "Element already loaded: " + event.target.currentSrc);
+
+  elements.push(event.target);
+
+  --loadsWaiting;
+  parent.ok(true, "Loaded " + event.target.currentSrc);
+  if (loadsWaiting == 0) {
+    parent.loadedAll(elements);
+  }
+}
+
+for (var i = 0; i < parent.gSmallTests.length; ++i) {
+  var test = parent.gSmallTests[i];
+  var elemType = /^audio/.test(test.type) ? "audio" : "video";
+  // Associate these elements with the subframe's document
+  var e = document.createElement(elemType);
+  if (e.canPlayType(test.type)) {
+    e.src = test.name;
+    e.addEventListener("loadeddata", loadedData, false);
+    e.load();
+    ++loadsWaiting;
+  }
+}
+
+if (loadsWaiting == 0) {
+  parent.todo(false, "Can't play anything");
+} else {
+  parent.SimpleTest.waitForExplicitFinish();
+}
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_reactivate.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test reactivation of a media element from a dead document</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+
+<iframe id="frame" src="reactivate_helper.html"></iframe>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var testsWaiting;
+var elements;
+
+function didPlay(event) {
+  ok(true, "Element ended: " + event.target.currentSrc);
+
+  --testsWaiting;
+  if (testsWaiting == 0) {
+    SimpleTest.finish();
+  }
+}
+
+function reviveElements() {
+  // reactivate the elements by moving them to our document
+  for (var i = 0; i < elements.length; ++i) {
+    document.body.appendChild(elements[i]);
+    elements[i].addEventListener("ended", didPlay, false);
+  }
+
+  testsWaiting = elements.length;
+}
+
+function loadedAll(elementList) {
+  elements = elementList;
+
+  // Blow away the subframe
+  document.body.removeChild(document.getElementById("frame"));
+
+  // Start the elements playing.
+  for (var i = 0; i < elements.length; ++i) {
+    elements[i].play();
+  }
+
+  setTimeout(reviveElements, 2000);
+}
+</script>
+</pre>
+</body>
+</html>
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -6979,25 +6979,16 @@ nsresult
 PresShell::RemoveOverrideStyleSheet(nsIStyleSheet *aSheet)
 {
   return mStyleSet->RemoveStyleSheet(nsStyleSet::eOverrideSheet, aSheet);
 }
 
 static void
 FreezeElement(nsIContent *aContent, void *aShell)
 {
-#ifdef MOZ_MEDIA
-  nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aContent));
-  if (domMediaElem) {
-    nsHTMLMediaElement* mediaElem = static_cast<nsHTMLMediaElement*>(aContent);
-    mediaElem->Freeze();
-    return;
-  }
-#endif
-
   nsIPresShell* shell = static_cast<nsIPresShell*>(aShell);
   nsIFrame *frame = shell->FrameManager()->GetPrimaryFrameFor(aContent, -1);
   nsIObjectFrame *objectFrame = do_QueryFrame(frame);
   if (objectFrame) {
     objectFrame->StopPlugin();
   }
 }
 
@@ -7047,25 +7038,16 @@ PresShell::FireOrClearDelayedEvents(PRBo
       mDelayedEvents.Clear();
     }
   }
 }
 
 static void
 ThawElement(nsIContent *aContent, void *aShell)
 {
-#ifdef MOZ_MEDIA
-  nsCOMPtr<nsIDOMHTMLMediaElement> domMediaElem(do_QueryInterface(aContent));
-  if (domMediaElem) {
-    nsHTMLMediaElement* mediaElem = static_cast<nsHTMLMediaElement*>(aContent);
-    mediaElem->Thaw();
-    return;
-  }
-#endif
-
   nsCOMPtr<nsIObjectLoadingContent> objlc(do_QueryInterface(aContent));
   if (objlc) {
     nsCOMPtr<nsIPluginInstance> inst;
     objlc->EnsureInstantiation(getter_AddRefs(inst));
   }
 }
 
 static PRBool