Bug 833382 - Implement WebVTTLoadListener r=cpearce,bz
authorRick Eyre <rick.eyre@hotmail.com>
Mon, 10 Jun 2013 08:30:00 -0700
changeset 134544 7c3e3ad77c89fe87f997dff1969d614e8ed00c1e
parent 134543 32a44be4703c65d605d433dd9dfe65cdc6450d76
child 134545 7e181e566679c3133b7a7f2b63c7c452579a2633
push id29261
push userrgiles@mozilla.com
push dateMon, 10 Jun 2013 18:47:19 +0000
treeherdermozilla-inbound@7c3e3ad77c89 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, bz
bugs833382
milestone24.0a1
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 833382 - Implement WebVTTLoadListener r=cpearce,bz - Implemented WebVTTLoadListener to manage the webvtt parser. - TextTrackCue now handles the conversion of webvtt nodes to anonymous content which will be displayed on the video div overlay. - HTMLTrackElement manages the lifetime of the WebVTTLoadListener.
content/html/content/src/HTMLTrackElement.cpp
content/html/content/src/HTMLTrackElement.h
content/html/content/src/Makefile.in
content/media/Makefile.in
content/media/TextTrack.cpp
content/media/TextTrack.h
content/media/TextTrackCue.cpp
content/media/TextTrackCue.h
content/media/TextTrackCueList.cpp
content/media/TextTrackCueList.h
content/media/TextTrackList.cpp
content/media/TextTrackList.h
content/media/VideoUtils.h
content/media/WebVTTLoadListener.cpp
content/media/WebVTTLoadListener.h
content/media/moz.build
layout/media/symbols.def.in
layout/style/html.css
--- a/content/html/content/src/HTMLTrackElement.cpp
+++ b/content/html/content/src/HTMLTrackElement.cpp
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "mozilla/dom/HTMLTrackElementBinding.h"
 #include "mozilla/dom/HTMLUnknownElement.h"
+#include "WebVTTLoadListener.h"
 #include "nsAttrValueInlines.h"
 #include "nsCOMPtr.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
@@ -82,23 +83,33 @@ HTMLTrackElement::~HTMLTrackElement()
 {
 }
 
 NS_IMPL_ELEMENT_CLONE(HTMLTrackElement)
 
 NS_IMPL_ADDREF_INHERITED(HTMLTrackElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLTrackElement, Element)
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED_3(HTMLTrackElement, nsGenericHTMLElement,
-                                     mTrack, mChannel, mMediaParent)
+NS_IMPL_CYCLE_COLLECTION_INHERITED_4(HTMLTrackElement, nsGenericHTMLElement,
+                                     mTrack, mChannel, mMediaParent,
+                                     mLoadListener)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(HTMLTrackElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLElement)
 NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
 
+void
+HTMLTrackElement::OnChannelRedirect(nsIChannel* aChannel,
+                                    nsIChannel* aNewChannel,
+                                    uint32_t aFlags)
+{
+  NS_ASSERTION(aChannel == mChannel, "Channels should match!");
+  mChannel = aNewChannel;
+}
+
 JSObject*
 HTMLTrackElement::WrapNode(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return HTMLTrackElementBinding::Wrap(aCx, aScope, this);
 }
 
 bool
 HTMLTrackElement::IsWebVTTEnabled()
@@ -114,22 +125,16 @@ HTMLTrackElement::Track()
     // an empty object to return if we don't already have one.
     mTrack = new TextTrack(OwnerDoc()->GetParentObject());
   }
 
   return mTrack;
 }
 
 void
-HTMLTrackElement::DisplayCueText(webvtt_node* head)
-{
-  // TODO: Bug 833382 - Propagate to the LoadListener.
-}
-
-void
 HTMLTrackElement::CreateTextTrack()
 {
   DOMString label, srcLang;
   GetSrclang(srcLang);
   GetLabel(label);
   mTrack = new TextTrack(OwnerDoc()->GetParentObject(), Kind(), label, srcLang);
 
   if (mMediaParent) {
@@ -186,16 +191,95 @@ HTMLTrackElement::ParseAttribute(int32_t
 
   // Otherwise call the generic implementation.
   return nsGenericHTMLElement::ParseAttribute(aNamespaceID,
                                               aAttribute,
                                               aValue,
                                               aResult);
 }
 
+void
+HTMLTrackElement::LoadResource()
+{
+  // Find our 'src' url
+  nsAutoString src;
+  if (!GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
+    return;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NewURIFromString(src, getter_AddRefs(uri));
+  NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+  LOG(PR_LOG_ALWAYS, ("%p Trying to load from src=%s", this,
+      NS_ConvertUTF16toUTF8(src).get()));
+
+  if (mChannel) {
+    mChannel->Cancel(NS_BINDING_ABORTED);
+    mChannel = nullptr;
+  }
+
+  rv = nsContentUtils::GetSecurityManager()->
+    CheckLoadURIWithPrincipal(NodePrincipal(), uri,
+                              nsIScriptSecurityManager::STANDARD);
+  NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+
+  int16_t shouldLoad = nsIContentPolicy::ACCEPT;
+  rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_MEDIA,
+                                 uri,
+                                 NodePrincipal(),
+                                 static_cast<nsGenericHTMLElement*>(this),
+                                 NS_LITERAL_CSTRING("text/vtt"), // mime type
+                                 nullptr, // extra
+                                 &shouldLoad,
+                                 nsContentUtils::GetContentPolicy(),
+                                 nsContentUtils::GetSecurityManager());
+  NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+  if (NS_CP_REJECTED(shouldLoad)) {
+    return;
+  }
+
+  CreateTextTrack();
+
+  // Check for a Content Security Policy to pass down to the channel
+  // created to load the media content.
+  nsCOMPtr<nsIChannelPolicy> channelPolicy;
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
+  NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+  if (csp) {
+    channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
+    if (!channelPolicy) {
+      return;
+    }
+    channelPolicy->SetContentSecurityPolicy(csp);
+    channelPolicy->SetLoadType(nsIContentPolicy::TYPE_MEDIA);
+  }
+  nsCOMPtr<nsIChannel> channel;
+  nsCOMPtr<nsILoadGroup> loadGroup = OwnerDoc()->GetDocumentLoadGroup();
+  rv = NS_NewChannel(getter_AddRefs(channel),
+                     uri,
+                     nullptr,
+                     loadGroup,
+                     nullptr,
+                     nsIRequest::LOAD_NORMAL,
+                     channelPolicy);
+  NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+
+  mLoadListener = new WebVTTLoadListener(this);
+  rv = mLoadListener->LoadResource();
+  NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+  channel->SetNotificationCallbacks(mLoadListener);
+
+  LOG(PR_LOG_DEBUG, ("opening webvtt channel"));
+  rv = channel->AsyncOpen(mLoadListener, nullptr);
+  NS_ENSURE_TRUE_VOID(NS_SUCCEEDED(rv));
+
+  mChannel = channel;
+}
+
 nsresult
 HTMLTrackElement::BindToTree(nsIDocument* aDocument,
                              nsIContent* aParent,
                              nsIContent* aBindingParent,
                              bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument,
                                                  aParent,
@@ -216,33 +300,18 @@ HTMLTrackElement::BindToTree(nsIDocument
   if (!mMediaParent) {
     mMediaParent = static_cast<HTMLMediaElement*>(aParent);
 
     HTMLMediaElement* media = static_cast<HTMLMediaElement*>(aParent);
     // TODO: separate notification for 'alternate' tracks?
     media->NotifyAddedSource();
     LOG(PR_LOG_DEBUG, ("Track element sent notification to parent."));
 
-    // TODO: this section needs to become async in bug 833382.
-    // See https://bugzilla.mozilla.org/show_bug.cgi?id=833385#c55.
-
-    // Find our 'src' url
-    nsAutoString src;
-
-    // TODO: we might want to instead call LoadResource() in a
-    // AfterSetAttr, like we do in media element.
-    if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
-      nsCOMPtr<nsIURI> uri;
-      nsresult rvTwo = NewURIFromString(src, getter_AddRefs(uri));
-      if (NS_SUCCEEDED(rvTwo)) {
-        LOG(PR_LOG_ALWAYS, ("%p Trying to load from src=%s", this,
-        NS_ConvertUTF16toUTF8(src).get()));
-        // TODO: bug 833382 - dispatch a load request.
-      }
-    }
+    nsContentUtils::AddScriptRunner(
+      NS_NewRunnableMethod(this, &HTMLTrackElement::LoadResource));
   }
 
   return NS_OK;
 }
 
 void
 HTMLTrackElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
--- a/content/html/content/src/HTMLTrackElement.h
+++ b/content/html/content/src/HTMLTrackElement.h
@@ -15,21 +15,22 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMEventTarget.h"
 #include "nsIHttpChannel.h"
-#include "webvtt/node.h"
 
 namespace mozilla {
 namespace dom {
 
+class WebVTTLoadListener;
+
 class HTMLTrackElement MOZ_FINAL : public nsGenericHTMLElement
                                  , public nsIDOMHTMLElement
 {
 public:
   HTMLTrackElement(already_AddRefed<nsINodeInfo> aNodeInfo);
   virtual ~HTMLTrackElement();
 
   // nsISupports
@@ -125,28 +126,36 @@ public:
   // Override BindToTree() so that we can trigger a load when we become
   // the child of a media element.
   virtual nsresult BindToTree(nsIDocument* aDocument,
                               nsIContent* aParent,
                               nsIContent* aBindingParent,
                               bool aCompileEventHandlers) MOZ_OVERRIDE;
   virtual void UnbindFromTree(bool aDeep, bool aNullParent) MOZ_OVERRIDE;
 
-  void DisplayCueText(webvtt_node* head);
-
   // Check enabling preference.
   static bool IsWebVTTEnabled();
 
 protected:
   virtual JSObject* WrapNode(JSContext* aCx,
                              JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+  void OnChannelRedirect(nsIChannel* aChannel, nsIChannel* aNewChannel,
+                         uint32_t aFlags);
+  // Will open a new channel for the HTMLTrackElement's src attribute and load
+  // HTMLTrackElement's WebVTTLoadListener by calling WebVTTLoadListener's
+  // LoadResource().
+  void LoadResource();
+
+  friend class TextTrackCue;
+  friend class WebVTTLoadListener;
 
   nsRefPtr<TextTrack> mTrack;
   nsCOMPtr<nsIChannel> mChannel;
   nsRefPtr<HTMLMediaElement> mMediaParent;
+  nsRefPtr<WebVTTLoadListener> mLoadListener;
   uint16_t mReadyState;
 
   void CreateTextTrack();
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/content/html/content/src/Makefile.in
+++ b/content/html/content/src/Makefile.in
@@ -34,13 +34,14 @@ INCLUDES	+= \
 		-I$(srcdir)/../../../../layout/generic \
 		-I$(srcdir)/../../../../dom/base \
 		-I$(srcdir)/../../../../editor/libeditor/base \
 		-I$(srcdir)/../../../../editor/libeditor/text \
 		-I$(srcdir)/../../../../editor/txmgr/src \
 		-I$(srcdir)/../../../../netwerk/base/src \
 		-I$(srcdir) \
 		-I$(topsrcdir)/xpcom/ds \
+		-I$(topsrcdir)/content/media/ \
 		$(NULL)
 
 DEFINES		+= \
 		-D_IMPL_NS_LAYOUT \
 		$(NULL)
--- a/content/media/Makefile.in
+++ b/content/media/Makefile.in
@@ -17,11 +17,17 @@ FAIL_ON_WARNINGS := 1
 endif # !_MSC_VER
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
 
+INCLUDES  += \
+  -I$(topsrcdir)/content/base/src \
+  -I$(topsrcdir)/layout/generic \
+  -I$(topsrcdir)/layout/xul/base/src \
+  $(NULL)
+
 DEFINES  += -D_IMPL_NS_LAYOUT
 CFLAGS   += $(GSTREAMER_CFLAGS)
 CXXFLAGS += $(GSTREAMER_CFLAGS)
--- a/content/media/TextTrack.cpp
+++ b/content/media/TextTrack.cpp
@@ -41,19 +41,19 @@ TextTrack::TextTrack(nsISupports* aParen
   , mMode(TextTrackMode::Disabled)
   , mCueList(new TextTrackCueList(aParent))
   , mActiveCueList(new TextTrackCueList(aParent))
 {
   SetIsDOMBinding();
 }
 
 void
-TextTrack::Update(double time)
+TextTrack::Update(double aTime)
 {
-  mCueList->Update(time);
+  mCueList->Update(aTime);
 }
 
 JSObject*
 TextTrack::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return TextTrackBinding::Wrap(aCx, aScope, this);
 }
 
--- a/content/media/TextTrack.h
+++ b/content/media/TextTrack.h
@@ -77,17 +77,18 @@ public:
   TextTrackCueList* GetActiveCues() const
   {
     if (mMode == TextTrackMode::Disabled) {
       return nullptr;
     }
     return mActiveCueList;
   }
 
-  void Update(double time);
+  // Time is in seconds.
+  void Update(double aTime);
 
   void AddCue(TextTrackCue& aCue);
   void RemoveCue(TextTrackCue& aCue);
   void CueChanged(TextTrackCue& aCue);
 
   IMPL_EVENT_HANDLER(cuechange)
 
 private:
--- a/content/media/TextTrackCue.cpp
+++ b/content/media/TextTrackCue.cpp
@@ -1,25 +1,30 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/HTMLTrackElement.h"
 #include "mozilla/dom/TextTrackCue.h"
 #include "mozilla/dom/TextTrackCueBinding.h"
+#include "mozilla/dom/ProcessingInstruction.h"
+#include "nsIFrame.h"
+#include "nsTextNode.h"
+#include "nsVideoFrame.h"
 #include "webvtt/cue.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_3(TextTrackCue,
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_4(TextTrackCue,
                                         mGlobal,
                                         mTrack,
-                                        mTrackElement)
+                                        mTrackElement,
+                                        mCueDiv)
 
 NS_IMPL_ADDREF_INHERITED(TextTrackCue, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(TextTrackCue, nsDOMEventTargetHelper)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TextTrackCue)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 // Set cue setting defaults based on step 19 & seq.
 // in http://dev.w3.org/html5/webvtt/#parsing
@@ -73,21 +78,238 @@ TextTrackCue::~TextTrackCue()
 {
   if (mHead) {
     // Release our reference and null mHead.
     webvtt_release_node(&mHead);
   }
 }
 
 void
-TextTrackCue::DisplayCue()
+TextTrackCue::CreateCueOverlay()
+{
+  mTrackElement->OwnerDoc()->CreateElem(NS_LITERAL_STRING("div"), nullptr,
+                                        kNameSpaceID_XHTML,
+                                        getter_AddRefs(mCueDiv));
+  nsGenericHTMLElement* cueDiv =
+    static_cast<nsGenericHTMLElement*>(mCueDiv.get());
+  cueDiv->SetClassName(NS_LITERAL_STRING("caption-text"));
+}
+
+void
+TextTrackCue::RenderCue()
+{
+  nsRefPtr<DocumentFragment> frag = GetCueAsHTML();
+  if (!frag || !mTrackElement) {
+    return;
+  }
+
+  if (!mCueDiv) {
+    CreateCueOverlay();
+  }
+
+  HTMLMediaElement* parent = mTrackElement->mMediaParent;
+  if (!parent) {
+    return;
+  }
+
+  nsIFrame* frame = parent->GetPrimaryFrame();
+  if (!frame) {
+    return;
+  }
+
+  nsVideoFrame* videoFrame = do_QueryFrame(frame);
+  if (!videoFrame) {
+    return;
+  }
+
+  nsIContent* overlay =  videoFrame->GetCaptionOverlay();
+  if (!overlay) {
+    return;
+  }
+
+  ErrorResult rv;
+  nsContentUtils::SetNodeTextContent(overlay, EmptyString(), true);
+  nsContentUtils::SetNodeTextContent(mCueDiv, EmptyString(), true);
+
+  mCueDiv->AppendChild(*frag, rv);
+  overlay->AppendChild(*mCueDiv, rv);
+}
+
+already_AddRefed<DocumentFragment>
+TextTrackCue::GetCueAsHTML()
+{
+  nsRefPtr<DocumentFragment> frag =
+    mTrackElement->OwnerDoc()->CreateDocumentFragment();
+
+  ConvertNodeTreeToDOMTree(frag);
+
+  return frag.forget();
+}
+
+struct WebVTTNodeParentPair
+{
+  webvtt_node* mNode;
+  nsIContent* mParent;
+
+  WebVTTNodeParentPair(webvtt_node* aNode, nsIContent* aParent)
+    : mNode(aNode)
+    , mParent(aParent)
+  {}
+};
+
+static void
+PushChildren(nsTArray<WebVTTNodeParentPair> &aNodeParentPairStack,
+             webvtt_node* aNode, nsIContent* aParentContent)
+{
+  // Push on in reverse order so we process the nodes in the correct
+  // order -- left to right.
+  for (int i = aNode->data.internal_data->length; i > 0; i--) {
+    WebVTTNodeParentPair nodeParentPair(
+      aNode->data.internal_data->children[i - 1],
+      aParentContent);
+    aNodeParentPairStack.AppendElement(nodeParentPair);
+  }
+}
+
+static WebVTTNodeParentPair
+PopChild(nsTArray<WebVTTNodeParentPair> &aNodeParentPairStack) {
+  WebVTTNodeParentPair temp =
+    aNodeParentPairStack.LastElement();
+  aNodeParentPairStack.RemoveElementAt(aNodeParentPairStack.Length() - 1);
+  return temp;
+}
+
+void
+TextTrackCue::ConvertNodeTreeToDOMTree(nsIContent* aParentContent)
 {
-  if (mTrackElement) {
-    mTrackElement->DisplayCueText(mHead);
+  nsTArray<WebVTTNodeParentPair> nodeParentPairStack;
+
+  // mHead should actually be the head of a node tree.
+  if (mHead->kind != WEBVTT_HEAD_NODE) {
+    return;
+  }
+  // Seed the stack for traversal.
+  PushChildren(nodeParentPairStack, mHead, aParentContent);
+
+  while (!nodeParentPairStack.IsEmpty()) {
+    WebVTTNodeParentPair nodeParentPair = PopChild(nodeParentPairStack);
+    nsCOMPtr<nsIContent> content;
+    if (WEBVTT_IS_VALID_LEAF_NODE(nodeParentPair.mNode->kind)) {
+      content = ConvertLeafNodeToContent(nodeParentPair.mNode);
+    } else if (WEBVTT_IS_VALID_INTERNAL_NODE(nodeParentPair.mNode->kind)) {
+      content = ConvertInternalNodeToContent(nodeParentPair.mNode);
+      // Push the children of the current node onto the stack for traversal.
+      PushChildren(nodeParentPairStack, nodeParentPair.mNode, content);
+    }
+    if (content && nodeParentPair.mParent) {
+      ErrorResult rv;
+      nodeParentPair.mParent->AppendChild(*content, rv);
+    }
+  }
+}
+
+already_AddRefed<nsIContent>
+TextTrackCue::ConvertInternalNodeToContent(const webvtt_node* aWebVTTNode)
+{
+  nsIAtom* atom;
+
+  switch (aWebVTTNode->kind) {
+    case WEBVTT_BOLD:
+      atom = nsGkAtoms::b;
+      break;
+    case WEBVTT_ITALIC:
+      atom = nsGkAtoms::i;
+      break;
+    case WEBVTT_UNDERLINE:
+      atom = nsGkAtoms::u;
+      break;
+    case WEBVTT_RUBY:
+      atom = nsGkAtoms::ruby;
+      break;
+    case WEBVTT_RUBY_TEXT:
+      atom = nsGkAtoms::rt;
+      break;
+    case WEBVTT_VOICE:
+      atom = nsGkAtoms::span;
+      break;
+    case WEBVTT_CLASS:
+      atom = nsGkAtoms::span;
+      break;
+    default:
+      return nullptr;
+      break;
   }
+
+  nsCOMPtr<nsIContent> cueTextContent;
+  mTrackElement->OwnerDoc()->CreateElem(nsDependentAtomString(atom), nullptr,
+                                        kNameSpaceID_XHTML,
+                                        getter_AddRefs(cueTextContent));
+
+  if (aWebVTTNode->kind == WEBVTT_VOICE) {
+    const char* text =
+      webvtt_string_text(&aWebVTTNode->data.internal_data->annotation);
+    if (text) {
+      nsGenericHTMLElement* genericHtmlElement =
+        static_cast<nsGenericHTMLElement*>(cueTextContent.get());
+      genericHtmlElement->SetTitle(NS_ConvertUTF8toUTF16(text));
+    }
+  }
+
+  webvtt_stringlist* classes = aWebVTTNode->data.internal_data->css_classes;
+  if (classes && classes->items && classes->length > 0) {
+    nsAutoString classString;
+
+    const char *text = webvtt_string_text(classes->items);
+    if (text) {
+      AppendUTF8toUTF16(text, classString);
+      for (uint32_t i = 1; i < classes->length; i++) {
+        text = webvtt_string_text(classes->items + i);
+        if (text) {
+          classString.Append(' ');
+          AppendUTF8toUTF16(text, classString);
+        }
+      }
+    }
+
+    nsGenericHTMLElement* genericHtmlElement =
+      static_cast<nsGenericHTMLElement*>(cueTextContent.get());
+    genericHtmlElement->SetClassName(classString);
+  }
+  return cueTextContent.forget();
+}
+
+already_AddRefed<nsIContent>
+TextTrackCue::ConvertLeafNodeToContent(const webvtt_node* aWebVTTNode)
+{
+  nsCOMPtr<nsIContent> cueTextContent;
+  nsNodeInfoManager* nimgr = mTrackElement->NodeInfo()->NodeInfoManager();
+  switch (aWebVTTNode->kind) {
+    case WEBVTT_TEXT:
+    {
+      cueTextContent = new nsTextNode(nimgr);
+      const char* text = webvtt_string_text(&aWebVTTNode->data.text);
+      if (text) {
+        cueTextContent->SetText(NS_ConvertUTF8toUTF16(text), false);
+      }
+      break;
+    }
+    case WEBVTT_TIME_STAMP:
+    {
+      nsAutoString timeStamp;
+      timeStamp.AppendInt(aWebVTTNode->data.timestamp);
+      cueTextContent =
+          NS_NewXMLProcessingInstruction(nimgr, NS_LITERAL_STRING("timestamp"),
+                                         timeStamp);
+      break;
+    }
+    default:
+      return nullptr;
+      break;
+  }
+  return cueTextContent.forget();
 }
 
 JSObject*
 TextTrackCue::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return TextTrackCueBinding::Wrap(aCx, aScope, this);
 }
 
--- a/content/media/TextTrackCue.h
+++ b/content/media/TextTrackCue.h
@@ -242,21 +242,85 @@ public:
     return mId.Equals(rhs.mId);
   }
 
   const nsAString& Id() const
   {
     return mId;
   }
 
-  void DisplayCue();
+  /**
+   * Overview of WEBVTT cuetext and anonymous content setup.
+   *
+   * webvtt_nodes are the parsed version of WEBVTT cuetext. WEBVTT cuetext is
+   * the portion of a WEBVTT cue that specifies what the caption will actually
+   * show up as on screen.
+   *
+   * WEBVTT cuetext can contain markup that loosely relates to HTML markup. It
+   * can contain tags like <b>, <u>, <i>, <c>, <v>, <ruby>, <rt>, <lang>,
+   * including timestamp tags.
+   *
+   * When the caption is ready to be displayed the webvtt_nodes are converted
+   * over to anonymous DOM content. <i>, <u>, <b>, <ruby>, and <rt> all become
+   * HTMLElements of their corresponding HTML markup tags. <c> and <v> are
+   * converted to <span> tags. Timestamp tags are converted to XML processing
+   * instructions. Additionally, all cuetext tags support specifying of classes.
+   * This takes the form of <foo.class.subclass>. These classes are then parsed
+   * and set as the anonymous content's class attribute.
+   *
+   * Rules on constructing DOM objects from webvtt_nodes can be found here
+   * http://dev.w3.org/html5/webvtt/#webvtt-cue-text-dom-construction-rules.
+   * Current rules are taken from revision on April 15, 2013.
+   */
+
+  /**
+   * Converts the TextTrackCue's cuetext into a tree of DOM objects and attaches
+   * it to a div on it's owning TrackElement's MediaElement's caption overlay.
+   */
+  void RenderCue();
+
+  /**
+   * Produces a tree of anonymous content based on the tree of the processed
+   * cue text. This lives in a tree of C nodes whose head is mHead.
+   *
+   * Returns a DocumentFragment that is the head of the tree of anonymous
+   * content.
+   */
+  already_AddRefed<DocumentFragment> GetCueAsHTML();
+
+  /**
+   * Converts mHead to a list of DOM elements and attaches it to aParentContent.
+   *
+   * Processes the C node tree in a depth-first pre-order traversal and creates
+   * a mirrored DOM tree. The conversion rules come from the webvtt DOM
+   * construction rules:
+   * http://dev.w3.org/html5/webvtt/#webvtt-cue-text-dom-construction-rules
+   * Current rules taken from revision on May 13, 2013.
+   */
+  void
+  ConvertNodeTreeToDOMTree(nsIContent* aParentContent);
+
+  /**
+   * Converts an internal webvtt node, i.e. one that has children, to an
+   * anonymous HTMLElement.
+   */
+  already_AddRefed<nsIContent>
+  ConvertInternalNodeToContent(const webvtt_node* aWebVTTNode);
+
+  /**
+   * Converts a leaf webvtt node, i.e. one that does not have children, to
+   * either a text node or processing instruction.
+   */
+  already_AddRefed<nsIContent>
+  ConvertLeafNodeToContent(const webvtt_node* aWebVTTNode);
 
 private:
   void CueChanged();
   void SetDefaultCueSettings();
+  void CreateCueOverlay();
 
   nsCOMPtr<nsISupports> mGlobal;
   nsString mText;
   double mStartTime;
   double mEndTime;
 
   nsRefPtr<TextTrack> mTrack;
   nsRefPtr<HTMLTrackElement> mTrackElement;
@@ -264,14 +328,17 @@ private:
   nsString mId;
   int32_t mPosition;
   int32_t mSize;
   bool mPauseOnExit;
   bool mSnapToLines;
   nsString mVertical;
   int mLine;
   TextTrackCueAlign mAlign;
+
+  // Anonymous child which is appended to VideoFrame's caption display div.
+  nsCOMPtr<nsIContent> mCueDiv;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_TextTrackCue_h
--- a/content/media/TextTrackCueList.cpp
+++ b/content/media/TextTrackCueList.cpp
@@ -20,22 +20,22 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 TextTrackCueList::TextTrackCueList(nsISupports* aParent) : mParent(aParent)
 {
   SetIsDOMBinding();
 }
 
 void
-TextTrackCueList::Update(double time)
+TextTrackCueList::Update(double aTime)
 {
   const uint32_t length = mList.Length();
   for (uint32_t i = 0; i < length; i++) {
-    if (time > mList[i]->StartTime() && time < mList[i]->EndTime()) {
-      mList[i]->DisplayCue();
+    if (aTime > mList[i]->StartTime() && aTime < mList[i]->EndTime()) {
+      mList[i]->RenderCue();
     }
   }
 }
 
 JSObject*
 TextTrackCueList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return TextTrackCueListBinding::Wrap(aCx, aScope, this);
--- a/content/media/TextTrackCueList.h
+++ b/content/media/TextTrackCueList.h
@@ -35,17 +35,18 @@ public:
     return mParent;
   }
 
   uint32_t Length() const
   {
     return mList.Length();
   }
 
-  void Update(double time);
+  // Time is in seconds.
+  void Update(double aTime);
 
   TextTrackCue* IndexedGetter(uint32_t aIndex, bool& aFound);
   TextTrackCue* GetCueById(const nsAString& aId);
 
   void AddCue(TextTrackCue& cue);
   void RemoveCue(TextTrackCue& cue);
 
 private:
--- a/content/media/TextTrackList.cpp
+++ b/content/media/TextTrackList.cpp
@@ -16,16 +16,25 @@ NS_IMPL_RELEASE_INHERITED(TextTrackList,
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TextTrackList)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 TextTrackList::TextTrackList(nsISupports* aGlobal) : mGlobal(aGlobal)
 {
   SetIsDOMBinding();
 }
 
+void
+TextTrackList::Update(double aTime)
+{
+  uint32_t length = Length(), i;
+  for (i = 0; i < length; i++) {
+    mTextTracks[i]->Update(aTime);
+  }
+}
+
 JSObject*
 TextTrackList::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return TextTrackListBinding::Wrap(aCx, aScope, this);
 }
 
 TextTrack*
 TextTrackList::IndexedGetter(uint32_t aIndex, bool& aFound)
--- a/content/media/TextTrackList.h
+++ b/content/media/TextTrackList.h
@@ -33,22 +33,19 @@ public:
     return mGlobal;
   }
 
   uint32_t Length() const
   {
     return mTextTracks.Length();
   }
 
-  void Update(double time) {
-    uint32_t length = Length(), i;
-    for( i = 0; i < length; i++ ) {
-      mTextTracks[i]->Update(time);
-    }
-  }
+  // Time is in seconds.
+  void Update(double aTime);
+
   TextTrack* IndexedGetter(uint32_t aIndex, bool& aFound);
 
   already_AddRefed<TextTrack> AddTextTrack(TextTrackKind aKind,
                                            const nsAString& aLabel,
                                            const nsAString& aLanguage);
   void AddTextTrack(TextTrack* aTextTrack) {
     mTextTracks.AppendElement(aTextTrack);
   }
--- a/content/media/VideoUtils.h
+++ b/content/media/VideoUtils.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/Attributes.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/CheckedInt.h"
 
 #include "nsRect.h"
 #include "nsIThreadManager.h"
 #include "nsThreadUtils.h"
+#include "prtime.h"
 
 using mozilla::CheckedInt64;
 using mozilla::CheckedUint64;
 using mozilla::CheckedInt32;
 using mozilla::CheckedUint32;
 
 // This file contains stuff we'd rather put elsewhere, but which is
 // dependent on other changes which we don't want to wait for. We plan to
@@ -157,16 +158,19 @@ CheckedInt64 FramesToUsecs(int64_t aFram
 CheckedInt64 UsecsToFrames(int64_t aUsecs, uint32_t aRate);
 
 // Number of microseconds per second. 1e6.
 static const int64_t USECS_PER_S = 1000000;
 
 // Number of microseconds per millisecond.
 static const int64_t USECS_PER_MS = 1000;
 
+// Converts seconds to milliseconds.
+#define SECONDS_TO_MS(s) ((s) / PR_MSEC_PER_SEC)
+
 // The maximum height and width of the video. Used for
 // sanitizing the memory allocation of the RGB buffer.
 // The maximum resolution we anticipate encountering in the
 // wild is 2160p - 3840x2160 pixels.
 static const int32_t MAX_VIDEO_WIDTH = 4000;
 static const int32_t MAX_VIDEO_HEIGHT = 3000;
 
 // Scales the display rect aDisplay by aspect ratio aAspectRatio.
new file mode 100644
--- /dev/null
+++ b/content/media/WebVTTLoadListener.cpp
@@ -0,0 +1,248 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebVTTLoadListener.h"
+#include "mozilla/dom/TextTrackCue.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "VideoUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_1(WebVTTLoadListener, mElement)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebVTTLoadListener)
+  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+  NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
+  NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebVTTLoadListener)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebVTTLoadListener)
+
+#ifdef PR_LOGGING
+PRLogModuleInfo* gTextTrackLog;
+# define LOG(...) PR_LOG(gTextTrackLog, PR_LOG_DEBUG, (__VA_ARGS__))
+#else
+# define LOG(msg)
+#endif
+
+WebVTTLoadListener::WebVTTLoadListener(HTMLTrackElement* aElement)
+  : mElement(aElement)
+{
+  MOZ_ASSERT(mElement, "Must pass an element to the callback");
+#ifdef PR_LOGGING
+  if (!gTextTrackLog) {
+    gTextTrackLog = PR_NewLogModule("TextTrack");
+  }
+#endif
+  LOG("WebVTTLoadListener created.");
+}
+
+WebVTTLoadListener::~WebVTTLoadListener()
+{
+  LOG("WebVTTLoadListener destroyed.");
+}
+
+nsresult
+WebVTTLoadListener::LoadResource()
+{
+  if (!HTMLTrackElement::IsWebVTTEnabled()) {
+    NS_WARNING("WebVTT support disabled."
+               " See media.webvtt.enabled in about:config. ");
+    return NS_ERROR_FAILURE;
+  }
+
+  LOG("Loading text track resource.");
+  webvtt_parser_t* parser = nullptr;
+  webvtt_status status = webvtt_create_parser(&OnParsedCueWebVTTCallBack,
+                                              &OnReportErrorWebVTTCallBack,
+                                              this, &parser);
+
+  if (status != WEBVTT_SUCCESS) {
+    NS_ENSURE_TRUE(status != WEBVTT_OUT_OF_MEMORY,
+                   NS_ERROR_OUT_OF_MEMORY);
+    NS_ENSURE_TRUE(status != WEBVTT_INVALID_PARAM,
+                   NS_ERROR_INVALID_ARG);
+    return NS_ERROR_FAILURE;
+  }
+
+  mParser.own(parser);
+  NS_ENSURE_TRUE(mParser != nullptr, NS_ERROR_FAILURE);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WebVTTLoadListener::OnStartRequest(nsIRequest* aRequest,
+                                   nsISupports* aContext)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WebVTTLoadListener::OnStopRequest(nsIRequest* aRequest,
+                                  nsISupports* aContext,
+                                  nsresult aStatus)
+{
+  webvtt_finish_parsing(mParser);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WebVTTLoadListener::OnDataAvailable(nsIRequest* aRequest,
+                                    nsISupports* aContext,
+                                    nsIInputStream* aStream,
+                                    uint64_t aOffset,
+                                    uint32_t aCount)
+{
+  uint32_t count = aCount;
+  while (count > 0) {
+    uint32_t read;
+    nsresult rv = aStream->ReadSegments(ParseChunk, this, count, &read);
+    NS_ENSURE_SUCCESS(rv, rv);
+    if (!read) {
+      return NS_ERROR_FAILURE;
+    }
+    count -= read;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WebVTTLoadListener::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+                                           nsIChannel* aNewChannel,
+                                           uint32_t aFlags,
+                                           nsIAsyncVerifyRedirectCallback* cb)
+{
+  if (mElement) {
+    mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+WebVTTLoadListener::GetInterface(const nsIID &aIID,
+                                 void** aResult)
+{
+  return QueryInterface(aIID, aResult);
+}
+
+NS_METHOD
+WebVTTLoadListener::ParseChunk(nsIInputStream* aInStream, void* aClosure,
+                               const char* aFromSegment, uint32_t aToOffset,
+                               uint32_t aCount, uint32_t* aWriteCount)
+{
+  WebVTTLoadListener* loadListener = static_cast<WebVTTLoadListener*>(aClosure);
+
+  if (WEBVTT_FAILED(webvtt_parse_chunk(loadListener->mParser, aFromSegment,
+                                       aCount))) {
+    LOG("Unable to parse chunk of WEBVTT text. Aborting.");
+    *aWriteCount = 0;
+    return NS_ERROR_FAILURE;
+  }
+  *aWriteCount = aCount;
+  return NS_OK;
+}
+
+void
+WebVTTLoadListener::OnParsedCue(webvtt_cue* aCue)
+{
+  const char* text = webvtt_string_text(&aCue->body);
+
+  nsRefPtr<TextTrackCue> textTrackCue =
+    new TextTrackCue(mElement->OwnerDoc()->GetParentObject(),
+                     SECONDS_TO_MS(aCue->from), SECONDS_TO_MS(aCue->until),
+                     NS_ConvertUTF8toUTF16(text), mElement,
+                     aCue->node_head);
+
+  text = webvtt_string_text(&aCue->id);
+  textTrackCue->SetId(NS_ConvertUTF8toUTF16(text));
+
+  textTrackCue->SetSnapToLines(aCue->snap_to_lines);
+  textTrackCue->SetSize(aCue->settings.size);
+  textTrackCue->SetPosition(aCue->settings.position);
+  textTrackCue->SetLine(aCue->settings.line);
+
+  nsAutoString vertical;
+  switch (aCue->settings.vertical) {
+    case WEBVTT_VERTICAL_LR:
+      vertical = NS_LITERAL_STRING("lr");
+      break;
+    case WEBVTT_VERTICAL_RL:
+      vertical = NS_LITERAL_STRING("rl");
+      break;
+    case WEBVTT_HORIZONTAL:
+      // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=865407
+      // Will be handled in the processing model.
+      break;
+  }
+  textTrackCue->SetVertical(vertical);
+
+  TextTrackCueAlign align;
+  switch (aCue->settings.align) {
+    case WEBVTT_ALIGN_START:
+      align = TextTrackCueAlign::Start;
+      break;
+    case WEBVTT_ALIGN_MIDDLE:
+      align = TextTrackCueAlign::Middle;
+    case WEBVTT_ALIGN_END:
+      align = TextTrackCueAlign::End;
+    case WEBVTT_ALIGN_LEFT:
+      align = TextTrackCueAlign::Left;
+      break;
+    case WEBVTT_ALIGN_RIGHT:
+      align = TextTrackCueAlign::Right;
+      break;
+    default:
+      align = TextTrackCueAlign::Start;
+      break;
+  }
+  textTrackCue->SetAlign(align);
+
+  mElement->mTrack->AddCue(*textTrackCue);
+}
+
+void
+WebVTTLoadListener::OnReportError(uint32_t aLine, uint32_t aCol,
+                                  webvtt_error aError)
+{
+#ifdef PR_LOGGING
+  // Get source webvtt file to display in the log
+  DOMString wideFile;
+  mElement->GetSrc(wideFile);
+
+  NS_ConvertUTF16toUTF8 file(wideFile);
+
+  const char* error = "parser error";
+  if (aError >= 0) {
+    error = webvtt_strerror(aError);
+  }
+
+  LOG("error: %s(%d:%d) - %s\n", file.get(), aLine, aCol, error);
+#endif
+}
+
+void WEBVTT_CALLBACK
+WebVTTLoadListener::OnParsedCueWebVTTCallBack(void* aUserData, webvtt_cue* aCue)
+{
+  WebVTTLoadListener* self = static_cast<WebVTTLoadListener*>(aUserData);
+  self->OnParsedCue(aCue);
+}
+
+int WEBVTT_CALLBACK
+WebVTTLoadListener::OnReportErrorWebVTTCallBack(void* aUserData, uint32_t aLine,
+                                                uint32_t aCol,
+                                                webvtt_error aError)
+{
+  WebVTTLoadListener* self = static_cast<WebVTTLoadListener*>(aUserData);
+  self->OnReportError(aLine, aCol, aError);
+  return WEBVTT_SUCCESS;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/media/WebVTTLoadListener.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_WebVTTLoadListener_h
+#define mozilla_dom_WebVTTLoadListener_h
+
+#include "nsIStreamListener.h"
+#include "nsIChannelEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsAutoPtr.h"
+#include "nsAutoRef.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/dom/HTMLTrackElement.h"
+#include "webvtt/parser.h"
+#include "webvtt/util.h"
+
+template <>
+class nsAutoRefTraits<webvtt_parser_t> : public nsPointerRefTraits<webvtt_parser_t>
+{
+public:
+  static void Release(webvtt_parser_t* aParser) { webvtt_delete_parser(aParser); }
+};
+
+namespace mozilla {
+namespace dom {
+/**
+ * Class that manages the libwebvtt parsing library and functions as an
+ * interface between Gecko and libwebvtt.
+ *
+ * Currently it's only designed to work with an HTMLTrackElement. The
+ * HTMLTrackElement controls the lifetime of the WebVTTLoadListener.
+ *
+ * The workflow of this class is as follows:
+ *  - Gets Loaded via an HTMLTrackElement class.
+ *  - As the HTMLTrackElement class gets new data WebVTTLoadListener's
+ *    OnDataAvailable() function is called and data is passed to the libwebvtt
+ *    parser.
+ *  - When the libwebvtt parser has finished a cue it will call the callbacks
+ *    that are exposed by the WebVTTLoadListener -- OnParsedCueWebVTTCallBack or
+ *    OnReportErrorWebVTTCallBack if it has encountered an error.
+ *  - When it has returned a cue successfully the WebVTTLoadListener will create
+ *    a new TextTrackCue and add it to the HTMLTrackElement's TextTrack.
+ */
+class WebVTTLoadListener MOZ_FINAL : public nsIStreamListener,
+                                     public nsIChannelEventSink,
+                                     public nsIInterfaceRequestor
+{
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+  NS_DECL_NSICHANNELEVENTSINK
+  NS_DECL_NSIINTERFACEREQUESTOR
+
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(WebVTTLoadListener,
+                                           nsIStreamListener)
+
+public:
+  WebVTTLoadListener(HTMLTrackElement* aElement);
+  ~WebVTTLoadListener();
+  void OnParsedCue(webvtt_cue* aCue);
+  void OnReportError(uint32_t aLine, uint32_t aCol, webvtt_error aError);
+  // Loads the libwebvtt parser. Must call this function in order to the
+  // WebVTTLoadListener to be ready to accept data.
+  nsresult LoadResource();
+
+private:
+  static NS_METHOD ParseChunk(nsIInputStream* aInStream, void* aClosure,
+                              const char* aFromSegment, uint32_t aToOffset,
+                              uint32_t aCount, uint32_t* aWriteCount);
+
+  nsRefPtr<HTMLTrackElement> mElement;
+  nsAutoRef<webvtt_parser_t> mParser;
+
+  static void WEBVTT_CALLBACK OnParsedCueWebVTTCallBack(void* aUserData,
+                                                        webvtt_cue* aCue);
+  static int WEBVTT_CALLBACK OnReportErrorWebVTTCallBack(void* aUserData,
+                                                         uint32_t aLine,
+                                                         uint32_t aCol,
+                                                         webvtt_error aError);
+};
+
+
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebVTTLoadListener_h
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -107,9 +107,10 @@ CPP_SOURCES += [
     'TextTrack.cpp',
     'TextTrackCue.cpp',
     'TextTrackCueList.cpp',
     'TextTrackList.cpp',
     'VideoFrameContainer.cpp',
     'VideoSegment.cpp',
     'VideoStreamTrack.cpp',
     'VideoUtils.cpp',
+    'WebVTTLoadListener.cpp',
 ]
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -571,14 +571,15 @@ hb_unicode_funcs_create
 hb_unicode_funcs_get_empty
 hb_unicode_funcs_set_combining_class_func
 hb_unicode_funcs_set_compose_func
 hb_unicode_funcs_set_decompose_func
 hb_unicode_funcs_set_eastasian_width_func
 hb_unicode_funcs_set_general_category_func
 hb_unicode_funcs_set_mirroring_func
 hb_unicode_funcs_set_script_func
-webvtt_string_text
 webvtt_create_parser
 webvtt_delete_parser
+webvtt_finish_parsing
 webvtt_parse_chunk
 webvtt_ref_node
 webvtt_release_node
+webvtt_string_text
--- a/layout/style/html.css
+++ b/layout/style/html.css
@@ -722,26 +722,36 @@ audio:not([controls]) {
 
 *|*::-moz-html-canvas-content {
   display: block !important;
   /* we want to be an absolute and fixed container */
   -moz-transform: translate(0) !important;
 }
 
 video > .caption-box {
-  text-align: center;
-  font-weight: bold;
-  font-size: 24px;
+  position: relative;
   pointer-events: none;
 }
 
 video > div .caption-text {
-  color: gold;
-  background-color: rgba(105,105,105,0.4);
+  position: absolute;
+  bottom: 24px;
+  padding: 5px;
+  left: 0;
+  right: 0;
+  text-align: center;
   pointer-events: auto;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 20px;
+  color: white;
+  text-shadow:
+    -2px -2px 1px #000,
+    2px -2px 1px #000,
+    -2px 2px 1px #000,
+    2px 2px 1px #000;
 }
 
 /* emulation of non-standard HTML <marquee> tag */
 marquee {
   width: -moz-available;
   display: inline-block;
   vertical-align: text-bottom;
   text-align: start;