Bug 548523 - Replace HTMLMediaElement.autobuffer attribute with 'preload'. r=roc a=blocking2.0
☠☠ backed out by 898826f52402 ☠ ☠
authorRich Dougherty <rich@rd.gen.nz>, Chris Pearce <chris@pearce.org.nz>
Wed, 18 Aug 2010 10:49:14 +1200
changeset 50752 42c8cd0e1a657f40917c72941609a5defffb2ed2
parent 50751 7eee00899270a137b263dc156c3b002272eb2906
child 50753 f88258e1ddbe9a12024d403dd62a6d1142af7829
child 50764 898826f524023eb4ac5d3ca7f3b3329449808812
push idunknown
push userunknown
push dateunknown
reviewersroc, blocking2
bugs548523
milestone2.0b5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 548523 - Replace HTMLMediaElement.autobuffer attribute with 'preload'. r=roc a=blocking2.0
content/base/src/nsContentSink.cpp
content/base/src/nsGkAtomList.h
content/html/content/public/nsHTMLMediaElement.h
content/html/content/src/nsHTMLAudioElement.cpp
content/html/content/src/nsHTMLMediaElement.cpp
content/media/test/Makefile.in
content/media/test/test_audio1.html
content/media/test/test_audio2.html
content/media/test/test_autobuffer.html
content/media/test/test_autobuffer2.html
content/media/test/test_bug493187.html
content/media/test/test_load.html
content/media/test/test_preload_actions.html
content/media/test/test_preload_attribute.html
content/media/test/test_preload_suspend.html
content/media/test/test_resume.html
dom/interfaces/html/nsIDOMHTMLMediaElement.idl
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -1807,17 +1807,16 @@ nsIAtom** const kDefaultAllowedAttribute
   &nsGkAtoms::accept,
   &nsGkAtoms::acceptcharset,
   &nsGkAtoms::accesskey,
   &nsGkAtoms::action,
   &nsGkAtoms::align,
   &nsGkAtoms::alt,
   &nsGkAtoms::autocomplete,
 #ifdef MOZ_MEDIA
-  &nsGkAtoms::autobuffer,
   &nsGkAtoms::autoplay,
 #endif
   &nsGkAtoms::axis,
   &nsGkAtoms::background,
   &nsGkAtoms::bgcolor,
   &nsGkAtoms::border,
   &nsGkAtoms::cellpadding,
   &nsGkAtoms::cellspacing,
@@ -1867,16 +1866,17 @@ nsIAtom** const kDefaultAllowedAttribute
 #ifdef MOZ_MEDIA
   &nsGkAtoms::pixelratio,
   &nsGkAtoms::playbackrate,
   &nsGkAtoms::playcount,
 #endif
   &nsGkAtoms::pointSize,
 #ifdef MOZ_MEDIA
   &nsGkAtoms::poster,
+  &nsGkAtoms::preload,
 #endif
   &nsGkAtoms::prompt,
   &nsGkAtoms::readonly,
   &nsGkAtoms::rel,
   &nsGkAtoms::rev,
   &nsGkAtoms::role,
   &nsGkAtoms::rows,
   &nsGkAtoms::rowspan,
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -115,19 +115,16 @@ GK_ATOM(ascending, "ascending")
 GK_ATOM(aside, "aside")
 GK_ATOM(aspectRatio, "aspect-ratio")
 GK_ATOM(assign, "assign")
 GK_ATOM(async, "async")
 GK_ATOM(attribute, "attribute")
 GK_ATOM(attributeSet, "attribute-set")
 GK_ATOM(aural, "aural")
 GK_ATOM(_auto, "auto")
-#ifdef MOZ_MEDIA
-GK_ATOM(autobuffer, "autobuffer")
-#endif
 GK_ATOM(autocheck, "autocheck")
 GK_ATOM(autocomplete, "autocomplete")
 GK_ATOM(autofocus, "autofocus")
 #ifdef MOZ_MEDIA
 GK_ATOM(autoplay, "autoplay")
 #endif
 GK_ATOM(autorepeatbutton, "autorepeatbutton")
 GK_ATOM(axis, "axis")
@@ -759,16 +756,19 @@ GK_ATOM(position, "position")
 #ifdef MOZ_MEDIA
 GK_ATOM(poster, "poster")
 #endif
 GK_ATOM(pre, "pre")
 GK_ATOM(preceding, "preceding")
 GK_ATOM(precedingSibling, "preceding-sibling")
 GK_ATOM(predicate, "predicate")
 GK_ATOM(prefix, "prefix")
+#ifdef MOZ_MEDIA
+GK_ATOM(preload, "preload")
+#endif
 GK_ATOM(preserve, "preserve")
 GK_ATOM(preserveSpace, "preserve-space")
 GK_ATOM(preventdefault, "preventdefault")
 GK_ATOM(primary, "primary")
 GK_ATOM(print, "print")
 GK_ATOM(priority, "priority")
 GK_ATOM(processingInstruction, "processing-instruction")
 GK_ATOM(profile, "profile")
--- a/content/html/content/public/nsHTMLMediaElement.h
+++ b/content/html/content/public/nsHTMLMediaElement.h
@@ -425,16 +425,61 @@ protected:
    * Call this to reevaluate whether we should be holding a self-reference.
    */
   void AddRemoveSelfReference();
 
   /**
    * Called asynchronously to release a self-reference to this element.
    */
   void DoRemoveSelfReference();
+  
+  /**
+   * Possible values of the 'preload' attribute.
+   */
+  enum PreloadAttrValue {
+    PRELOAD_ATTR_EMPTY,    // set to ""
+    PRELOAD_ATTR_NONE,     // set to "none"
+    PRELOAD_ATTR_METADATA, // set to "metadata"
+    PRELOAD_ATTR_AUTO      // set to "auto"
+  };
+
+  /**
+   * The preloading action to perform. These dictate how we react to the 
+   * preload attribute. See mPreloadAction.
+   */
+  enum PreloadAction {
+    PRELOAD_UNDEFINED = 0, // not determined - used only for initialization
+    PRELOAD_NONE = 1,      // do not preload
+    PRELOAD_METADATA = 2,  // preload only the metadata (and first frame)
+    PRELOAD_ENOUGH = 3     // preload enough data to allow uninterrupted
+                           // playback
+  };
+
+  /**
+   * Suspends the load of resource at aURI, so that it can be resumed later
+   * by ResumeLoad(). This is called when we have a media with a 'preload'
+   * attribute value of 'none', during the resource selection algorithm.
+   */
+  void SuspendLoad(nsIURI* aURI);
+
+  /**
+   * Resumes a previously suspended load (suspended by SuspendLoad(uri)).
+   * Will continue running the resource selection algorithm.
+   * Sets mPreloadAction to aAction.
+   */
+  void ResumeLoad(PreloadAction aAction);
+
+  /**
+   * Handle a change to the preload attribute. Should be called whenever the
+   * value (or presence) of the preload attribute changes. The change in 
+   * attribute value may cause a change in the mPreloadAction of this
+   * element. If there is a change then this method will initiate any
+   * behaviour that is necessary to implement the action.
+   */
+  void UpdatePreloadAction();
 
   nsRefPtr<nsMediaDecoder> mDecoder;
 
   // A reference to the ImageContainer which contains the current frame
   // of video to display.
   nsRefPtr<ImageContainer> mImageContainer;
 
   // Holds a reference to the first channel we open to the media resource.
@@ -477,20 +522,33 @@ protected:
 
   // When the load algorithm is waiting for more src/<source>, this denotes
   // what type of waiting we're doing.
   LoadAlgorithmState mLoadWaitStatus;
 
   // Current audio volume
   float mVolume;
 
+  // If we're loading a preload:none media, we'll record the URI we're
+  // attempting to load in mPreloadURI, and delay loading the resource until
+  // the user initiates a load by either playing the resource, or explicitly
+  // loading it.
+  nsCOMPtr<nsIURI> mPreloadURI;
+  
+  // Stores the current preload action for this element. Initially set to
+  // PRELOAD_UNDEFINED, its value is changed by calling
+  // UpdatePreloadAction().
+  PreloadAction mPreloadAction;
+
   // Size of the media. Updated by the decoder on the main thread if
   // it changes. Defaults to a width and height of -1 if not set.
   nsIntSize mMediaSize;
 
+  nsRefPtr<gfxASurface> mPrintSurface;
+
   // If true then we have begun downloading the media content.
   // Set to false when completed, or not yet started.
   PRPackedBool mBegun;
 
   // True when the decoder has loaded enough data to display the
   // first frame of the content.
   PRPackedBool mLoadedFirstFrame;
 
@@ -546,32 +604,38 @@ protected:
   // an error occurs, or the first frame is loaded.
   PRPackedBool mDelayingLoadEvent;
 
   // PR_TRUE when we've got a task queued to call SelectResource(),
   // or while we're running SelectResource().
   PRPackedBool mIsRunningSelectResource;
 
   // PR_TRUE if we suspended the decoder because we were paused,
-  // autobuffer and autoplay were not set, and we loaded the first frame.
+  // preloading metadata is enabled, autoplay was not enabled, and we loaded
+  // the first frame.
   PRPackedBool mSuspendedAfterFirstFrame;
 
   // 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.
+  // preloading metdata was enabled, autoplay was not enabled, 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;
 
   // PR_TRUE if we've received a notification that the engine is shutting
   // down.
   PRPackedBool mShuttingDown;
 
-  nsRefPtr<gfxASurface> mPrintSurface;
+  // PR_TRUE if we've suspended a load in the resource selection algorithm
+  // due to loading a preload:none media. When PR_TRUE, the resource we'll
+  // load when the user initiates either playback or an explicit load is
+  // stored in mPreloadURI.
+  PRPackedBool mLoadIsSuspended;
 };
 
 #endif
--- a/content/html/content/src/nsHTMLAudioElement.cpp
+++ b/content/html/content/src/nsHTMLAudioElement.cpp
@@ -115,19 +115,20 @@ nsHTMLAudioElement::~nsHTMLAudioElement(
 {
 }
 
 NS_IMETHODIMP
 nsHTMLAudioElement::Initialize(nsISupports* aOwner, JSContext* aContext,
                                JSObject *aObj, PRUint32 argc, jsval *argv)
 {
   // Audio elements created using "new Audio(...)" should have
-  // 'autobuffer' set (since the script must intend to play the audio)
-  nsresult rv = SetAttr(kNameSpaceID_None, nsGkAtoms::autobuffer,
-                        NS_LITERAL_STRING("autobuffer"), PR_TRUE);
+  // 'preload' set to 'auto' (since the script must intend to
+  // play the audio)
+  nsresult rv = SetAttr(kNameSpaceID_None, nsGkAtoms::preload,
+                        NS_LITERAL_STRING("auto"), PR_TRUE);
   if (NS_FAILED(rv))
     return rv;
 
   if (argc <= 0) {
     // Nothing more to do here if we don't get any arguments.
     return NS_OK;
   }
 
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -393,17 +393,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsHTMLMediaElement)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
 // nsIDOMHTMLMediaElement
 NS_IMPL_URI_ATTR(nsHTMLMediaElement, Src, src)
 NS_IMPL_BOOL_ATTR(nsHTMLMediaElement, Controls, controls)
 NS_IMPL_BOOL_ATTR(nsHTMLMediaElement, Autoplay, autoplay)
-NS_IMPL_BOOL_ATTR(nsHTMLMediaElement, Autobuffer, autobuffer)
+NS_IMPL_STRING_ATTR(nsHTMLMediaElement, Preload, preload)
 
 /* readonly attribute nsIDOMHTMLMediaElement mozAutoplayEnabled; */
 NS_IMETHODIMP nsHTMLMediaElement::GetMozAutoplayEnabled(PRBool *aAutoplayEnabled)
 {
   *aAutoplayEnabled = mAutoplayEnabled;
 
   return NS_OK;
 }
@@ -605,16 +605,23 @@ void nsHTMLMediaElement::SelectResource(
   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));
     if (NS_SUCCEEDED(rv)) {
       LOG(PR_LOG_DEBUG, ("%p Trying load from src=%s", this, NS_ConvertUTF16toUTF8(src).get()));
       mIsLoadingFromSrcAttribute = PR_TRUE;
+      if (mPreloadAction == nsHTMLMediaElement::PRELOAD_NONE) {
+        // preload:none media, suspend the load here before we make any
+        // network requests.
+        SuspendLoad(uri);
+        return;
+      }
+
       rv = LoadResource(uri);
       if (NS_SUCCEEDED(rv))
         return;
     }
     NoSupportedMediaSourceError();
   } else {
     // Otherwise, the source elements will be used.
     LoadFromSourceChildren();
@@ -642,25 +649,136 @@ void nsHTMLMediaElement::LoadFromSourceC
       // the media element.
       mLoadWaitStatus = WAITING_FOR_SOURCE;
       NoSupportedMediaSourceError();
       return;
     }
 
     mNetworkState = nsIDOMHTMLMediaElement::NETWORK_LOADING;
 
+    if (mPreloadAction == nsHTMLMediaElement::PRELOAD_NONE) {
+      // preload:none media, suspend the load here before we make any
+      // network requests.
+      SuspendLoad(uri);
+      return;
+    }
+
     rv = LoadResource(uri);
     if (NS_SUCCEEDED(rv))
       return;
 
     // If we fail to load, loop back and try loading the next resource.
   }
   NS_NOTREACHED("Execution should not reach here!");
 }
 
+void nsHTMLMediaElement::SuspendLoad(nsIURI* aURI)
+{
+  mLoadIsSuspended = PR_TRUE;
+  mPreloadURI = aURI;
+  mNetworkState = nsIDOMHTMLMediaElement::NETWORK_IDLE;
+  DispatchAsyncProgressEvent(NS_LITERAL_STRING("suspend"));
+  ChangeDelayLoadStatus(PR_FALSE);
+}
+
+void nsHTMLMediaElement::ResumeLoad(PreloadAction aAction)
+{
+  NS_ASSERTION(mLoadIsSuspended, "Can only resume preload if halted for one");
+  nsCOMPtr<nsIURI> uri = mPreloadURI;
+  mLoadIsSuspended = PR_FALSE;
+  mPreloadURI = nsnull;
+  mPreloadAction = aAction;
+  ChangeDelayLoadStatus(PR_TRUE);
+  if (mIsLoadingFromSrcAttribute) {
+    // We were loading from the element's src attribute.
+    if (NS_FAILED(LoadResource(uri))) {
+      NoSupportedMediaSourceError();
+    }
+  } else {
+    // We were loading from a child <source> element. Try to resume the
+    // load of that child, and if that fails, try the next child.
+    if (NS_FAILED(LoadResource(uri))) {
+      LoadFromSourceChildren();
+    }
+  }
+}
+
+static PRBool IsAutoplayEnabled()
+{
+  return nsContentUtils::GetBoolPref("media.autoplay.enabled");
+}
+
+void nsHTMLMediaElement::UpdatePreloadAction()
+{
+  PreloadAction nextAction = PRELOAD_UNDEFINED;
+  // If autoplay is set, we should always preload data, as we'll need it
+  // to play.
+  if (IsAutoplayEnabled() && HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) {
+    nextAction = nsHTMLMediaElement::PRELOAD_ENOUGH;
+  } else {
+    // Find the appropriate preload action by looking at the attribute.
+    const nsAttrValue* val = mAttrsAndChildren.GetAttr(nsGkAtoms::preload,
+                                                       kNameSpaceID_None);
+    if (!val) {
+      // Attribute is not set. The default is to load metadata.
+      nextAction = nsHTMLMediaElement::PRELOAD_METADATA;
+    } else if (val->Type() == nsAttrValue::eEnum) {
+      PreloadAttrValue attr = static_cast<PreloadAttrValue>(val->GetEnumValue());
+      if (attr == nsHTMLMediaElement::PRELOAD_ATTR_EMPTY ||
+          attr == nsHTMLMediaElement::PRELOAD_ATTR_AUTO)
+      {
+        nextAction = nsHTMLMediaElement::PRELOAD_ENOUGH;
+      } else if (attr == nsHTMLMediaElement::PRELOAD_ATTR_METADATA) {
+        nextAction = nsHTMLMediaElement::PRELOAD_METADATA;
+      } else if (attr == nsHTMLMediaElement::PRELOAD_ATTR_NONE) {
+        nextAction = nsHTMLMediaElement::PRELOAD_NONE;
+      }
+    } else {
+      // There was a value, but it wasn't an enumerated value.
+      // Use the suggested "missing value default" of "metadata".
+      nextAction = nsHTMLMediaElement::PRELOAD_METADATA;
+    }
+  }
+
+  if ((mBegun || mIsRunningSelectResource) && nextAction < mPreloadAction) {
+    // We've started a load or are already downloading, and the preload was
+    // changed to a state where we buffer less. We don't support this case,
+    // so don't change the preload behaviour.
+    return;
+  }
+
+  PRBool wasPreloadNone = mPreloadAction == PRELOAD_NONE;
+  mPreloadAction = nextAction;
+  if (nextAction == nsHTMLMediaElement::PRELOAD_ENOUGH) {
+    if (mLoadIsSuspended) {
+      // Our load was previouly suspended due to the media having preload
+      // value "none". The preload value has changed to preload:auto, so
+      // resume the load.
+      ResumeLoad(PRELOAD_ENOUGH);
+    } else {
+      // Preload as much of the video as we can, i.e. don't suspend after
+      // the first frame.
+      StopSuspendingAfterFirstFrame();
+    }
+
+  } else if (nextAction == nsHTMLMediaElement::PRELOAD_METADATA) {
+    // Ensure that the video can be suspended after first frame.
+    mAllowSuspendAfterFirstFrame = PR_TRUE;
+    if (mLoadIsSuspended) {
+      // Our load was previouly suspended due to the media having preload
+      // value "none". The preload value has changed to preload:metadata, so
+      // resume the load. We'll pause the load again after we've read the
+      // metadata.
+      ResumeLoad(PRELOAD_METADATA);
+    }
+  }
+
+  return;
+}
+
 nsresult nsHTMLMediaElement::LoadResource(nsIURI* aURI)
 {
   NS_ASSERTION(mDelayingLoadEvent,
                "Should delay load event (if in document) during load");
   nsresult rv;
 
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
@@ -989,17 +1107,19 @@ nsHTMLMediaElement::nsHTMLMediaElement(a
     mIsRunningLoadMethod(PR_FALSE),
     mIsLoadingFromSrcAttribute(PR_FALSE),
     mDelayingLoadEvent(PR_FALSE),
     mIsRunningSelectResource(PR_FALSE),
     mSuspendedAfterFirstFrame(PR_FALSE),
     mAllowSuspendAfterFirstFrame(PR_TRUE),
     mHasPlayedOrSeeked(PR_FALSE),
     mHasSelfReference(PR_FALSE),
-    mShuttingDown(PR_FALSE)
+    mShuttingDown(PR_FALSE),
+    mPreloadAction(PRELOAD_UNDEFINED),
+    mLoadIsSuspended(PR_FALSE)
 {
 #ifdef PR_LOGGING
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
   }
@@ -1054,16 +1174,18 @@ void nsHTMLMediaElement::SetPlayedOrSeek
 NS_IMETHODIMP nsHTMLMediaElement::Play()
 {
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(PR_TRUE);
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
     nsresult rv = Load();
     NS_ENSURE_SUCCESS(rv, rv);
+  }  else if (mLoadIsSuspended) {
+    ResumeLoad(PRELOAD_ENOUGH);
   } else if (mDecoder) {
     if (mDecoder->IsEnded()) {
       SetCurrentTime(0);
     }
     if (!mPausedForInactiveDocument) {
       nsresult rv = mDecoder->Play();
       NS_ENSURE_SUCCESS(rv, rv);
     }
@@ -1094,97 +1216,113 @@ NS_IMETHODIMP nsHTMLMediaElement::Play()
   return NS_OK;
 }
 
 PRBool nsHTMLMediaElement::ParseAttribute(PRInt32 aNamespaceID,
                                           nsIAtom* aAttribute,
                                           const nsAString& aValue,
                                           nsAttrValue& aResult)
 {
+  // Mappings from 'preload' attribute strings to an enumeration.
+  static const nsAttrValue::EnumTable kPreloadTable[] = {
+    { "",         nsHTMLMediaElement::PRELOAD_ATTR_EMPTY },
+    { "none",     nsHTMLMediaElement::PRELOAD_ATTR_NONE },
+    { "metadata", nsHTMLMediaElement::PRELOAD_ATTR_METADATA },
+    { "auto",     nsHTMLMediaElement::PRELOAD_ATTR_AUTO },
+    { 0 }
+  };
+
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::src) {
       static const char* kWhitespace = " \n\r\t\b";
       aResult.SetTo(nsContentUtils::TrimCharsInSet(kWhitespace, aValue));
       return PR_TRUE;
     }
     else if (aAttribute == nsGkAtoms::loopstart
             || aAttribute == nsGkAtoms::loopend
             || aAttribute == nsGkAtoms::start
             || aAttribute == nsGkAtoms::end) {
       return aResult.ParseFloatValue(aValue);
     }
     else if (ParseImageAttribute(aAttribute, aValue, aResult)) {
       return PR_TRUE;
     }
+    else if (aAttribute == nsGkAtoms::preload) {
+      return aResult.ParseEnumValue(aValue, kPreloadTable, PR_FALSE);
+    }
   }
 
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 nsresult nsHTMLMediaElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom* aName,
                                      nsIAtom* aPrefix, const nsAString& aValue,
                                      PRBool aNotify)
 {
   nsresult rv =
     nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue,
                                     aNotify);
+  if (NS_FAILED(rv))
+    return rv;
   if (aNotify && aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::src) {
       if (mLoadWaitStatus == WAITING_FOR_SRC_OR_SOURCE) {
         // A previous load algorithm instance is waiting on a src
         // addition, resume the load. It is waiting at "step 1 of the load
         // algorithm".
         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();
+      UpdatePreloadAction();
+    } else if (aName == nsGkAtoms::preload) {
+      UpdatePreloadAction();
     }
   }
 
   return rv;
 }
 
 nsresult nsHTMLMediaElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttr,
                                        PRBool aNotify)
 {
   nsresult rv = nsGenericHTMLElement::UnsetAttr(aNameSpaceID, aAttr, aNotify);
+  if (NS_FAILED(rv))
+    return rv;
   if (aNotify && aNameSpaceID == kNameSpaceID_None) {
     if (aAttr == nsGkAtoms::autoplay) {
       // This attribute can affect AddRemoveSelfReference
       AddRemoveSelfReference();
+      UpdatePreloadAction();
+    } else if (aAttr == nsGkAtoms::preload) {
+      UpdatePreloadAction();
     }
-    // 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)
 {
   if (aDocument) {
     mIsBindingToTree = PR_TRUE;
     mAutoplayEnabled =
       IsAutoplayEnabled() && (!aDocument || !aDocument->IsStaticDocument());
+    // The preload action depends on the value of the autoplay attribute.
+    // It's value may have changed, so update it.
+    UpdatePreloadAction();
   }
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
                                                  aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   if (aDocument) {
     if (NS_SUCCEEDED(rv) &&
         mIsDoneAddingChildren &&
@@ -1636,17 +1774,17 @@ void nsHTMLMediaElement::FirstFrameLoade
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA);
   ChangeDelayLoadStatus(PR_FALSE);
 
   NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
 
   if (mDecoder && mAllowSuspendAfterFirstFrame && mPaused &&
       !aResourceFullyLoaded &&
       !HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
-      !HasAttr(kNameSpaceID_None, nsGkAtoms::autobuffer)) {
+      mPreloadAction == nsHTMLMediaElement::PRELOAD_METADATA) {
     mSuspendedAfterFirstFrame = PR_TRUE;
     mDecoder->Suspend();
   }
 }
 
 void nsHTMLMediaElement::ResourceLoaded()
 {
   mBegun = PR_FALSE;
@@ -1939,16 +2077,17 @@ nsresult nsHTMLMediaElement::DispatchPro
   return target->DispatchEvent(event, &dummy);
 }
 
 nsresult nsHTMLMediaElement::DoneAddingChildren(PRBool aHaveNotified)
 {
   if (!mIsDoneAddingChildren) {
     mIsDoneAddingChildren = PR_TRUE;
 
+    UpdatePreloadAction();
     if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
       QueueSelectResourceTask();
     }
   }
 
   return NS_OK;
 }
 
--- a/content/media/test/Makefile.in
+++ b/content/media/test/Makefile.in
@@ -86,17 +86,16 @@ include $(topsrcdir)/config/rules.mk
 		seek7.js \
 		seek8.js \
 		seek9.js \
 		seek10.js \
 		seek11.js \
 		test_access_control.html \
 		test_audio1.html \
 		test_audio2.html \
-		test_autobuffer.html \
 		test_autoplay.html \
 		test_buffered.html \
 		test_bug448534.html \
 		test_bug463162.xhtml \
 		test_bug465498.html \
 		test_bug493187.html \
 		test_bug495145.html \
 		test_bug495300.html \
@@ -142,17 +141,17 @@ include $(topsrcdir)/config/rules.mk
 		$(NULL)
 
 # These tests are disabled until we figure out random failures.
 # When these tests are fixed, we should also make them backend-independent.
 #		test_resume.html \
 # Bug 492821:
 #   test_videoDocumentTitle.html
 # Bug 493692:
-#   test_autobuffer2.html
+#   test_preload_suspend.html
 # Disabled since we don't play Wave files standalone, for now
 #		test_audioDocumentTitle.html
 
 ifdef MOZ_OGG
 ifneq ($(OS_ARCH),WINNT)
 # Disabled on windows until we figure out the random failures.
 # When this is fixed, make it backend-independent please.
 # See bug 475369 and bug 526323
--- a/content/media/test/test_audio1.html
+++ b/content/media/test/test_audio1.html
@@ -11,15 +11,15 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 for (var i = 0; i < gAudioTests.length; ++i) {
   var test = gAudioTests[i];
   var a1 = new Audio();
   if (!a1.canPlayType(test.type))
     continue;
 
-  is(a1.getAttribute("autobuffer"), "autobuffer", "Autobuffer automatically set");
+  is(a1.getAttribute("preload"), "auto", "preload:auto automatically set");
   is(a1.src, "", "Src set?");
 }
 </script>
 </pre>
 </body>
 </html>
--- a/content/media/test/test_audio2.html
+++ b/content/media/test/test_audio2.html
@@ -12,15 +12,15 @@
 <script class="testbody" type="text/javascript">
 var tmpAudio = new Audio();
 for (var i = 0; i < gAudioTests.length; ++i) {
   var test = gAudioTests[i];
   if (!tmpAudio.canPlayType(test.type))
     continue;
 
   var a1 = new Audio(test.name);
-  is(a1.getAttribute("autobuffer"), "autobuffer", "Autobuffer automatically set");
+  is(a1.getAttribute("preload"), "auto", "Preload automatically set to auto");
   is(a1.src.match("/[^/]+$"), "/" + test.name, "src OK");
 }
 </script>
 </pre>
 </body>
 </html>
--- a/content/media/test/test_bug493187.html
+++ b/content/media/test/test_bug493187.html
@@ -49,17 +49,17 @@ for (var i=0; i<gSeekTests.length; ++i) 
 
   var v = document.createElement('video');
   if (!v.canPlayType(test.type))
     continue;
   v.src = test.name;
   v._name = test.name;
   v._seeked = false;
   v._finished = false;
-  v.autobuffer = true;
+  v.preload = "auto";
   v.addEventListener("loadedmetadata", start, false);
   v.addEventListener("canplaythrough", canPlayThrough, false);
   v.addEventListener("seeking", startSeeking, false);
   document.body.appendChild(v);
   videos.push(v);
 }
 
 if (videos.length == 0) {
--- a/content/media/test/test_load.html
+++ b/content/media/test/test_load.html
@@ -47,17 +47,17 @@ function listener(evt) {
     setTimeout(nextTest, 0); 
   }
 }
 
 var gMedia = null;
 
 function createMedia(tag) {
   gMedia = document.createElement(tag);
-  gMedia.setAttribute("autobuffer", "true");
+  gMedia.setAttribute("preload", "auto");
   for (var i=0; i<gEventTypes.length; i++) {
     gMedia.addEventListener(gEventTypes[i], listener, false);
   }
 }
 
 function addSource(src, type) {
   var s = document.createElement("source");
   s.src = src;
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_preload_actions.html
@@ -0,0 +1,548 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=548523
+-->
+<head>
+  <title>Test for Bug 548523</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="text/javascript" src="manifest.js"></script>
+  <script type="text/javascript" src="use_large_cache.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=548523">Mozilla Bug 548523</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<!-- <button onClick="SimpleTest.finish();">Finish</button> -->
+<div>Tests complete: <span id="log" style="font-size: small;"></span></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 548523 **/
+
+var videos = [];
+
+var test = getPlayableVideo(gSeekTests);
+var bogusSrc = "bogus.duh";
+var bogusType = "video/bogus";
+var gotLoadEvent = false;
+var finished = false;
+
+addLoadEvent(function() {gotLoadEvent=true;});
+
+function log(m) {
+  var l = document.getElementById("log");
+  l.innerHTML += m;
+}
+
+function maybeFinish(n) {
+  log(n + ",");
+  setTimeout(
+  function() {
+    if (!finished && AllFinished(videos)) {
+      finished = true;
+      is(gotLoadEvent, true, "Should not have delayed the load event indefinitely");
+      SimpleTest.finish();
+    }
+  }, 0);
+}
+
+function filename(uri) {
+  return uri.substr(uri.lastIndexOf("/")+1);
+}
+
+// Every test must have a setup(v) function, and must set _finished field on target v to
+// true when test is complete.
+var tests = [
+  {
+    // 1. Add preload:none video with src to document. Load should halt at NETWORK_IDLE and HAVE_NOTHING,
+    // after receiving a suspend event. Should not receive loaded events until after we call load().
+    // Note the suspend event is explictly sent by our "stop the load" code, but other tests can't rely
+    // on it for the preload:metadata case, as there can be multiple suspend events when loading metadata.
+    suspend:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadStart, true, "(1) Must get loadstart.");
+      is(v._gotLoadedMetaData, false, "(1) Must not get loadedmetadata.");
+      is(v.readyState, v.HAVE_NOTHING, "(1) ReadyState must be HAVE_NOTHING");
+      is(v.networkState, v.NETWORK_IDLE, "(1) NetworkState must be NETWORK_IDLE");
+      v._finished = true;
+      maybeFinish(1);
+    },
+    
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "none";
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("suspend", this.suspend, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none.
+    },
+  },
+  {
+    // 2. Add preload:metadata video with src to document. Should halt with NETWORK_IDLE, HAVE_CURRENT_DATA
+    // after suspend event and after loadedmetadata.
+    loadeddata:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadStart, true, "(2) Must get loadstart.");
+      is(v._gotLoadedMetaData, true, "(2) Must get loadedmetadata.");
+      ok(v.readyState >= v.HAVE_CURRENT_DATA, "(2) ReadyState must be >= HAVE_CURRENT_DATA");
+      is(v.networkState, v.NETWORK_IDLE, "(2) NetworkState must be NETWORK_IDLE");
+      v._finished = true;
+      maybeFinish(2);
+    },
+    
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "metadata";
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadeddata", this.loadeddata, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load, which will be halted after
+                                     // metadata due to preload:metadata.
+    },
+  },
+  {
+    // 3. Add preload:auto to document. Should receive canplaythrough eventually.
+    canplaythrough:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadStart, true, "(3) Must get loadstart.");
+      is(v._gotLoadedMetaData, true, "(3) Must get loadedmetadata.");
+      v._finished = true;
+      maybeFinish(3);
+    },
+    
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "auto";
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("canplaythrough", this.canplaythrough, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+    },
+  },
+  {
+    // 4. Add preload:none video to document. Call play(), should load then play through.
+    suspend:
+    function(e) {
+      var v = e.target;
+      if (v._gotSuspend) 
+        return; // We can receive multiple suspend events, like the one after download completes.
+      v._gotSuspend = true;
+      is(v._gotLoadStart, true, "(4) Must get loadstart.");
+      is(v._gotLoadedMetaData, false, "(4) Must not get loadedmetadata.");
+      is(v.readyState, v.HAVE_NOTHING, "(4) ReadyState must be HAVE_NOTHING");
+      is(v.networkState, v.NETWORK_IDLE, "(4) NetworkState must be NETWORK_IDLE");
+      v.play(); // Should load and play through.
+    },
+    
+    ended:
+    function(e) {
+      ok(true, "(4) Got playback ended");
+      e.target._finished = true;
+      maybeFinish(4);
+    },
+      
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._gotSuspend = false;
+      v._finished = false;
+      v.preload = "none";
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("suspend", this.suspend, false);
+      v.addEventListener("ended", this.ended, false);
+      v.src = test.name;
+      document.body.appendChild(v);
+    },
+  },
+  {
+    // 5. preload:none video without resource, add to document, will implicitly start a
+    // preload:none load. Add a src, it shouldn't load.
+    suspend:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadStart, true, "(5) Must get loadstart.");
+      is(v._gotLoadedMetaData, false, "(5) Must not get loadedmetadata.");
+      is(v.readyState, v.HAVE_NOTHING, "(5) ReadyState must be HAVE_NOTHING");
+      is(v.networkState, v.NETWORK_IDLE, "(5) NetworkState must be NETWORK_IDLE");
+      v._finished = true;
+      maybeFinish(5);
+    },
+      
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "none";
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("suspend", this.suspend, false);
+      document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource.
+      v.src = test.name; // Load should start, and halt at preload:none.
+    },
+  },
+  {
+    // 6. preload:none video without resource, add to document, will implicitly start a
+    // preload:none load. Add a source, it shouldn't load.
+    suspend:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadStart, true, "(6) Must get loadstart.");
+      is(v._gotLoadedMetaData, false, "(6) Must not get loadedmetadata.");
+      is(v.readyState, v.HAVE_NOTHING, "(6) ReadyState must be HAVE_NOTHING");
+      is(v.networkState, v.NETWORK_IDLE, "(6) NetworkState must be NETWORK_IDLE");
+      v._finished = true;
+      maybeFinish(6);
+    },
+      
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "none";
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("suspend", this.suspend, false);
+      document.body.appendChild(v); // Causes implicit load, which will be halted due to no resource.
+      var s = document.createElement("source");
+      s.src = test.name;
+      s.type = test.type;
+      v.appendChild(s); // Load should start, and halt at preload:none.
+    },
+  },
+  {
+    // 7. create a preload:none document with multiple sources, the first of which is invalid.
+    // Add to document, then play. It should load and play through the second source.
+    suspend:
+    function(e) {
+      var v = e.target;
+      if (v._gotSuspend) 
+        return; // We can receive multiple suspend events, like the one after download completes.
+      v._gotSuspend = true;
+      is(v._gotLoadStart, true, "(7) Must get loadstart.");
+      is(v._gotLoadedMetaData, false, "(7) Must not get loadedmetadata.");
+      is(v.readyState, v.HAVE_NOTHING, "(7) ReadyState must be HAVE_NOTHING");
+      is(v.networkState, v.NETWORK_IDLE, "(7) NetworkState must be NETWORK_IDLE");
+      v.play(); // Should load and play through.
+    },
+
+    ended:
+    function(e) {
+      ok(true, "(7) Got playback ended");
+      e.target._finished = true;
+      maybeFinish(7);
+    },
+      
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "none";
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("suspend", this.suspend, false);
+      v.addEventListener("ended", this.ended, false);
+      var s1 = document.createElement("source");
+      s1.src = "not-a-real-file.404"
+      s1.type = test.type;
+      v.appendChild(s1);
+      var s2 = document.createElement("source");
+      s2.src = test.name;
+      s2.type = test.type;
+      v.appendChild(s2);
+      document.body.appendChild(v); // Causes implicit load, which will be halt at preload:none on the second resource.
+    },
+  },
+  {
+    // 8. Change preload value from none to metadata should cause metadata to be loaded.
+    loadeddata:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadedMetaData, true, "(8) Must get loadedmetadata.");
+      ok(v.readyState >= v.HAVE_CURRENT_DATA, "(8) ReadyState must be >= HAVE_CURRENT_DATA on suspend.");
+      is(v.networkState, v.NETWORK_IDLE, "(8) NetworkState must be NETWORK_IDLE when load is halted");
+      v._finished = true;
+      maybeFinish(8);
+    },
+    
+    setup:
+    function(v) {
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "none";
+      v.addEventListener("loadstart", function(e){v.preload = "metadata";}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadeddata", this.loadeddata, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+    },
+  },
+  {
+    // 9. Change preload value from metadata to auto should cause entire media to be loaded.
+    canplaythrough:
+    function(e) {
+      var v = e.target;
+      if (v._finished)
+        return;
+      is(v._gotLoadStart, true, "(9) Must get loadstart.");
+      is(v._gotLoadedMetaData, true, "(9) Must get loadedmetadata.");
+      v._finished = true;
+      maybeFinish(9);
+    },
+    
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "metadata";
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadeddata", function(){v.preload = "auto"}, false);
+      v.addEventListener("canplaythrough", this.canplaythrough, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+    },
+  },
+  {
+    // 10. Change preload value from none to auto should cause entire media to be loaded.
+    canplaythrough:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadedMetaData, true, "(10) Must get loadedmetadata.");
+      v._finished = true;
+      maybeFinish(10);
+    },
+    
+    setup:
+    function(v) {
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "none";
+      v.addEventListener("loadstart", function(e){v.preload = "auto";}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("canplaythrough", this.canplaythrough, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+    },
+  },
+  {
+    // 11. Change preload value from none to metadata should cause metadata to load.
+    loadeddata:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadedMetaData, true, "(11) Must get loadedmetadata.");
+      ok(v.readyState >= v.HAVE_CURRENT_DATA, "(11) ReadyState must be >= HAVE_CURRENT_DATA.");
+      is(v.networkState, v.NETWORK_IDLE, "(11) NetworkState must be NETWORK_IDLE.");
+      v._finished = true;
+      maybeFinish(11);
+    },
+
+    setup:
+    function(v) {
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "none";
+      v.addEventListener("loadstart", function(e){v.preload = "metadata";}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadeddata", this.loadeddata, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+    },
+  },
+  {
+    // 12. Change preload value from auto to metadata after load started,
+    // should still do full load, should not halt after metadata only.
+    canplaythrough:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadedMetaData, true, "(12) Must get loadedmetadata.");
+      is(v._gotLoadStart, true, "(12) Must get loadstart.");
+      v._finished = true;
+      maybeFinish(12);
+    },
+
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "auto";
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("canplaythrough", this.canplaythrough, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+      v.preload = "metadata";
+    },
+  },
+  
+  {
+    // 13. Change preload value from metadata to none after load started,
+    // should still load up to metadata, should not halt immediately.
+    loadeddata:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadStart, true, "(13) Must get loadstart.");
+      is(v._gotLoadedMetaData, true, "(13) Must get loadedmetadata.");
+      ok(v.readyState >= v.HAVE_CURRENT_DATA, "(13) ReadyState must be >= HAVE_CURRENT_DATA.");
+      is(v.networkState, v.NETWORK_IDLE, "(13) NetworkState must be NETWORK_IDLE.");
+      v._finished = true;
+      maybeFinish(13);
+    },
+
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "metadata";
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("loadeddata", this.loadeddata, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+      v.preload = "none";
+    },
+  },
+  {
+    // 14. Add preload:metadata video with src to document. Play(), should play through.
+    loadeddata:
+    function(e) {
+      var v = e.target;
+      is(v._gotLoadStart, true, "(14) Must get loadstart.");
+      is(v._gotLoadedMetaData, true, "(14) Must get loadedmetadata.");
+      ok(v.readyState >= v.HAVE_CURRENT_DATA, "(14) ReadyState must be >= HAVE_CURRENT_DATA");
+      is(v.networkState, v.NETWORK_IDLE, "(14) NetworkState must be NETWORK_IDLE");
+      v.play();
+    },
+    
+    ended:
+    function(e) {
+      ok(true, "(14) Got playback ended");
+      e.target._finished = true;
+      maybeFinish(14);
+    },
+
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "metadata";
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("ended", this.ended, false);
+      v.addEventListener("loadeddata", this.loadeddata, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load, which will be halted after
+                                     // metadata due to preload:metadata.
+    },
+  },
+  {
+    // 15. Autoplay should override preload:none.
+    ended:
+    function(e) {
+      ok(true, "(15) Got playback ended.");
+      e.target._finished = true;
+      maybeFinish(15);
+    },
+    
+    setup:
+    function(v) {
+      v._gotLoadStart = false;
+      v._gotLoadedMetaData = false;
+      v._finished = false;
+      v.preload = "none";
+      v.autoplay = true;
+      v.addEventListener("loadstart", function(e){v._gotLoadStart = true;}, false);
+      v.addEventListener("loadedmetadata", function(e){v._gotLoadedMetaData = true;}, false);
+      v.addEventListener("ended", this.ended, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+    },
+  },
+  {
+    // 16. Autoplay should override preload:metadata.
+    ended:
+    function(e) {
+      ok(true, "(16) Got playback ended.");
+      e.target._finished = true;
+      maybeFinish(16);
+    },
+    
+    setup:
+    function(v) {
+      v._finished = false;
+      v.preload = "metadata";
+      v.autoplay = true;
+      v.addEventListener("ended", this.ended, false);
+      v.src = test.name;
+      document.body.appendChild(v); // Causes implicit load.
+    },
+  },
+  {
+    // 17. On a preload:none video, adding autoplay should disable preload none, i.e. don't break autoplay!
+    ended:
+    function(e) {
+      ok(true, "(17) Got playback ended.");
+      e.target._finished = true;
+      maybeFinish(17);
+    },
+    
+    setup:
+    function(v) {
+      v.addEventListener("ended", this.ended, false);
+      v._finished = false;
+      v.preload = "none";
+      document.body.appendChild(v); // Causes implicit load, which will be halted due to preload:none.
+      v.autoplay = true;
+      v.src = test.name;
+    },    
+  },
+];
+
+if (test) {
+  for (var i=0; i<tests.length; i++) {
+    var t = tests[i];
+    var v = document.createElement("video");
+    videos.push(v);
+    t.setup(v);
+  }
+}
+
+if (test && videos.length == 0) {
+  todo(false, "No types or tests supported");
+} else {
+  SimpleTest.waitForExplicitFinish();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
rename from content/media/test/test_autobuffer.html
rename to content/media/test/test_preload_attribute.html
--- a/content/media/test/test_autobuffer.html
+++ b/content/media/test/test_preload_attribute.html
@@ -13,33 +13,35 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479863">Mozilla Bug 479863</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 
 <video id='v1' onerror="event.stopPropagation();"></video><audio id='a1' onerror="ev
 ent.stopPropagation();"></audio>
-<video id='v2' onerror="event.stopPropagation();" autobuffer></video><audio id='a2' on
-error="event.stopPropagation();" autobuffer></audio>
+<video id='v2' onerror="event.stopPropagation();" preload="auto"></video><audio id='a2' on
+error="event.stopPropagation();" preload="auto"></audio>
 
 <pre id="test">
 <script type="application/javascript">
 var v1 = document.getElementById('v1');
 var a1 = document.getElementById('a1');
 var v2 = document.getElementById('v2');
 var a2 = document.getElementById('a2');
-ok(!v1.autobuffer, "v1.autobuffer should be false by default");
-ok(!a1.autobuffer, "v1.autobuffer should be false by default");
-ok(v2.autobuffer, "v2.autobuffer should be true");
-ok(a2.autobuffer, "v2.autobuffer should be true");
+is(v1.getAttribute("preload"), undefined, "video preload via getAttribute should be undefined by default");
+is(a1.getAttribute("preload"), undefined, "video preload via getAttribute should be undefined by default");
+is(v1.preload, "", "v1.preload should be undefined by default");
+is(a1.preload, "", "a1.preload should be undefined by default");
+is(v2.preload, "auto", "v2.preload should be auto");
+is(a2.preload, "auto", "a2.preload should be auto");
 
-v1.autobuffer = true;
-a1.autobuffer = true;
-ok(v1.autobuffer, "video.autobuffer not true");
-ok(a1.autobuffer, "audio.autobuffer not true");
-is(v1.getAttribute("autobuffer"), "", "video autobuffer attribute not set");
-is(a1.getAttribute("autobuffer"), "", "video autobuffer attribute not set");
+v1.preload = "auto";
+a1.preload = "auto";
+is(v1.preload, "auto", "video.preload should be auto");
+is(a1.preload, "auto", "audio.preload should be auto");
+is(v1.getAttribute("preload"), "auto", "video preload attribute should be auto");
+is(a1.getAttribute("preload"), "auto", "video preload attribute should be auto");
 
 </script>
 </pre>
 </body>
 </html>
rename from content/media/test/test_autobuffer2.html
rename to content/media/test/test_preload_suspend.html
--- a/content/media/test/test_autobuffer2.html
+++ b/content/media/test/test_preload_suspend.html
@@ -13,24 +13,26 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=479863">Mozilla Bug 479863</a>
 <p id="display"></p>
 <div id="content" style="display: none">
   
 </div>
 
 <video id='v1' onerror="event.stopPropagation()"></video>
-<video id='v2' onerror="event.stopPropagation()" autobuffer></video>
+<video id='v2' onerror="event.stopPropagation()" preload="auto"></video>
 <video id='v3' onerror="event.stopPropagation()" autoplay></video>
 <video id='v4' onerror="event.stopPropagation()"></video>
 <video id='v5' onerror="event.stopPropagation()"></video>
 <video id='v6' onerror="event.stopPropagation()"></video>
 
 <pre id="test">
 <script type="application/javascript">
+function is() {}
+
 var suspendCount = {};
 var expectedSuspendCount = {v1:2, v2:1, v3:1, v4:2, v5:2, v6:2};
 var totalSuspendCount = 0;
 for (var i = 0; i < expectedSuspendCount.length; ++i) {
   totalSuspendCount += expectedSuspendCount[i];
 }
 
 function suspended(event) {
@@ -53,17 +55,17 @@ function suspended(event) {
     }
     SimpleTest.finish();
   }
 
   if (suspendCount[event.target.id] > 1)
     return;
 
   if (event.target.id == "v1") {
-    event.target.autobuffer = true;
+    event.target.preload = "auto";
   } else if (event.target.id == "v4") {
     event.target.play();
   } else if (event.target.id == "v5") {
     event.target.currentTime = 0.1;
   } else if (event.target.id == "v6") {
     event.target.autoplay = true;
   }
 }
--- a/content/media/test/test_resume.html
+++ b/content/media/test/test_resume.html
@@ -2,17 +2,17 @@
 <html>
 <head>
   <title>Media test: Test resume of server-dropped connections</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>
-<audio autobuffer id="a"></audio>
+<audio preload="auto" id="a"></audio>
 <iframe id="f"></iframe>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 var key = Math.round(Math.random()*1000000000);
 var a = document.getElementById("a");
 var f = document.getElementById("f");
 
 function didEnd() {
--- a/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLMediaElement.idl
@@ -52,31 +52,32 @@
 
 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
 %{C++
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 %}
 
-[scriptable, uuid(b6c9f51d-237c-44d1-842d-996f4d62c843)]
+[scriptable, uuid(925c46e0-3795-11df-9879-0800200c9a66)]
 interface nsIDOMHTMLMediaElement : nsIDOMHTMLElement
 {
   // error state
   readonly attribute nsIDOMHTMLMediaError error;
 
   // network state
            attribute DOMString src;
   readonly attribute DOMString currentSrc;
   const unsigned short NETWORK_EMPTY = 0;
   const unsigned short NETWORK_IDLE = 1;
   const unsigned short NETWORK_LOADING = 2;
   const unsigned short NETWORK_LOADED = 3;
   const unsigned short NETWORK_NO_SOURCE = 4;
   readonly attribute unsigned short networkState;
+           attribute DomString preload;  
   readonly attribute nsIDOMHTMLTimeRanges buffered;
   void load();
   DOMString canPlayType(in DOMString type);
 
   // ready state
   const unsigned short HAVE_NOTHING = 0;
   const unsigned short HAVE_METADATA = 1;
   const unsigned short HAVE_CURRENT_DATA = 2;
@@ -87,17 +88,16 @@ interface nsIDOMHTMLMediaElement : nsIDO
 
   // playback state
            attribute float currentTime;
   readonly attribute float duration;
   readonly attribute boolean paused;
   readonly attribute boolean ended;
   readonly attribute boolean mozAutoplayEnabled;
            attribute boolean autoplay;
-           attribute boolean autobuffer;
   void play();
   void pause();
 
   // controls
            attribute boolean controls;
            attribute float volume;
            attribute boolean muted;