Bug 1507895 - Part I, Remove the videocontrols binding r=smaug
authorTimothy Guan-tin Chien <timdream@gmail.com>
Fri, 25 Jan 2019 13:12:26 +0000
changeset 455687 9c2a9b446836a91fe83e8aca3cc86c203b086f50
parent 455686 2ec1e0bc9843aaccafc9427afa07c5ef81f5ca92
child 455688 de0a1a2cdc12a19d78b59e9534dbc5a570903695
push id35457
push usercsabou@mozilla.com
push dateTue, 29 Jan 2019 09:20:40 +0000
treeherdermozilla-central@84104c5031c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1507895
milestone66.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 1507895 - Part I, Remove the videocontrols binding r=smaug This patch removes the XBL videocontrols binding and make <video> to always use the UA Widget to generate controls. DevTools tests that look for NAC is switched to use <input type=file>. Differential Revision: https://phabricator.services.mozilla.com/D17571
devtools/client/inspector/markup/test/browser_markup_anonymous_01.js
devtools/client/inspector/markup/test/browser_markup_anonymous_04.js
devtools/client/inspector/markup/test/doc_markup_anonymous.html
dom/html/HTMLMediaElement.cpp
dom/media/webvtt/vtt.jsm
dom/xbl/nsXBLService.cpp
dom/xul/nsXULElement.cpp
layout/generic/nsFrameIdList.h
layout/generic/nsVideoFrame.cpp
layout/generic/nsVideoFrame.h
layout/reftests/bugs/reftest.list
layout/style/res/html.css
mobile/android/tests/browser/chrome/test_mozAutoplayMediaBlocked.html
mobile/android/themes/geckoview/content.css
toolkit/content/jar.mn
toolkit/content/tests/moz.build
toolkit/content/tests/widgets/xbl/chrome.ini
toolkit/content/tests/widgets/xbl/head.js
toolkit/content/tests/widgets/xbl/mochitest.ini
toolkit/content/tests/widgets/xbl/test_audiocontrols_dimensions.html
toolkit/content/tests/widgets/xbl/test_bug898940.html
toolkit/content/tests/widgets/xbl/test_videocontrols.html
toolkit/content/tests/widgets/xbl/test_videocontrols_audio.html
toolkit/content/tests/widgets/xbl/test_videocontrols_audio_direction.html
toolkit/content/tests/widgets/xbl/test_videocontrols_error.html
toolkit/content/tests/widgets/xbl/test_videocontrols_iframe_fullscreen.html
toolkit/content/tests/widgets/xbl/test_videocontrols_jsdisabled.html
toolkit/content/tests/widgets/xbl/test_videocontrols_keyhandler.html
toolkit/content/tests/widgets/xbl/test_videocontrols_onclickplay.html
toolkit/content/tests/widgets/xbl/test_videocontrols_orientation.html
toolkit/content/tests/widgets/xbl/test_videocontrols_size.html
toolkit/content/tests/widgets/xbl/test_videocontrols_standalone.html
toolkit/content/tests/widgets/xbl/test_videocontrols_video_direction.html
toolkit/content/tests/widgets/xbl/test_videocontrols_video_noaudio.html
toolkit/content/tests/widgets/xbl/test_videocontrols_vtt.html
toolkit/content/widgets/videocontrols.xml
toolkit/themes/shared/media/videocontrols.css
xpcom/ds/StaticAtoms.py
--- a/devtools/client/inspector/markup/test/browser_markup_anonymous_01.js
+++ b/devtools/client/inspector/markup/test/browser_markup_anonymous_01.js
@@ -25,20 +25,20 @@ add_task(async function() {
   await isEditingMenuEnabled(span, inspector);
 
   info("Checking the ::after pseudo element");
   const after = children.nodes[2];
   await isEditingMenuDisabled(after, inspector);
 
   const native = await getNodeFront("#native", inspector);
 
-  // Markup looks like: <div><video controls /></div>
+  // Markup looks like: <div><input type="file"></div>
   const nativeChildren = await inspector.walker.children(native);
   is(nativeChildren.nodes.length, 1, "Children returned from walker");
 
-  info("Checking the video element");
-  const video = nativeChildren.nodes[0];
-  ok(!video.isAnonymous, "<video> is not anonymous");
+  info("Checking the input element");
+  const child = nativeChildren.nodes[0];
+  ok(!child.isAnonymous, "<input type=file> is not anonymous");
 
-  const videoChildren = await inspector.walker.children(video);
-  is(videoChildren.nodes.length, 0,
-    "No native children returned from walker for <video> by default");
+  const grandchildren = await inspector.walker.children(child);
+  is(grandchildren.nodes.length, 0,
+    "No native children returned from walker for <input type=file> by default");
 });
--- a/devtools/client/inspector/markup/test/browser_markup_anonymous_04.js
+++ b/devtools/client/inspector/markup/test/browser_markup_anonymous_04.js
@@ -11,27 +11,27 @@ const PREF = "devtools.inspector.showAll
 
 add_task(async function() {
   Services.prefs.setBoolPref(PREF, true);
 
   const {inspector} = await openInspectorForURL(TEST_URL);
 
   const native = await getNodeFront("#native", inspector);
 
-  // Markup looks like: <div><video controls /></div>
+  // Markup looks like: <div><input type="file"></div>
   const nativeChildren = await inspector.walker.children(native);
   is(nativeChildren.nodes.length, 1, "Children returned from walker");
 
-  info("Checking the video element");
-  const video = nativeChildren.nodes[0];
-  ok(!video.isAnonymous, "<video> is not anonymous");
+  info("Checking the input element");
+  const child = nativeChildren.nodes[0];
+  ok(!child.isAnonymous, "<input type=file> is not anonymous");
 
-  const videoChildren = await inspector.walker.children(video);
-  is(videoChildren.nodes.length, 3, "<video> has native anonymous children");
+  const grandchildren = await inspector.walker.children(child);
+  is(grandchildren.nodes.length, 2, "<input type=file> has native anonymous children");
 
-  for (const node of videoChildren.nodes) {
+  for (const node of grandchildren.nodes) {
     ok(node.isAnonymous, "Child is anonymous");
     ok(!node._form.isXBLAnonymous, "Child is not XBL anonymous");
     ok(!node._form.isShadowAnonymous, "Child is not shadow anonymous");
     ok(node._form.isNativeAnonymous, "Child is native anonymous");
     await isEditingMenuDisabled(node, inspector);
   }
 });
--- a/devtools/client/inspector/markup/test/doc_markup_anonymous.html
+++ b/devtools/client/inspector/markup/test/doc_markup_anonymous.html
@@ -15,17 +15,17 @@
     }
   </style>
 </head>
 <body>
   <div id="pseudo"><span>middle</span></div>
 
   <div id="shadow">light dom</div>
 
-  <div id="native"><video controls></video></div>
+  <div id="native"><input type="file"></div>
 
   <script>
   "use strict";
   var host = document.querySelector("#shadow");
   var root = host.attachShadow({ mode: "open" });
   root.innerHTML = "<h3>Shadow DOM</h3><select multiple></select>";
   </script>
 </body>
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -3974,18 +3974,17 @@ nsresult HTMLMediaElement::AfterSetAttr(
         UpdatePreloadAction();
       }
     } else if (aName == nsGkAtoms::preload) {
       UpdatePreloadAction();
     } else if (aName == nsGkAtoms::loop) {
       if (mDecoder) {
         mDecoder->SetLooping(!!aValue);
       }
-    } else if (nsContentUtils::IsUAWidgetEnabled() &&
-               aName == nsGkAtoms::controls && IsInComposedDoc()) {
+    } else if (aName == nsGkAtoms::controls && IsInComposedDoc()) {
       NotifyUAWidgetSetupOrChange();
     }
   }
 
   // Since AfterMaybeChangeAttr may call DoLoad, make sure that it is called
   // *after* any possible changes to mSrcMediaSource.
   if (aValue) {
     AfterMaybeChangeAttr(aNameSpaceID, aName, aNotify);
@@ -4013,17 +4012,17 @@ void HTMLMediaElement::AfterMaybeChangeA
   }
 }
 
 nsresult HTMLMediaElement::BindToTree(Document* aDocument, nsIContent* aParent,
                                       nsIContent* aBindingParent) {
   nsresult rv =
       nsGenericHTMLElement::BindToTree(aDocument, aParent, aBindingParent);
 
-  if (nsContentUtils::IsUAWidgetEnabled() && IsInComposedDoc()) {
+  if (IsInComposedDoc()) {
     // Construct Shadow Root so web content can be hidden in the DOM.
     AttachAndSetUAShadowRoot();
 #ifdef ANDROID
     NotifyUAWidgetSetupOrChange();
 #else
     // We don't want to call into JS if the website never asks for native
     // video controls.
     // If controls attribute is set later, controls is constructed lazily
@@ -4247,17 +4246,17 @@ void HTMLMediaElement::ReportTelemetry()
     }
   }
 }
 
 void HTMLMediaElement::UnbindFromTree(bool aDeep, bool aNullParent) {
   mUnboundFromTree = true;
   mVisibilityState = Visibility::UNTRACKED;
 
-  if (nsContentUtils::IsUAWidgetEnabled() && IsInComposedDoc()) {
+  if (IsInComposedDoc()) {
     NotifyUAWidgetTeardown();
   }
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   MOZ_ASSERT(IsHidden());
   NotifyDecoderActivityChanges();
 
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -1007,24 +1007,18 @@ ChromeUtils.import("resource://gre/modul
   WebVTT.processCues = function(window, cues, overlay, controls) {
     if (!window || !cues || !overlay) {
       return null;
     }
 
     var controlBar;
     var controlBarShown;
     if (controls) {
-      if (controls.localName == "videocontrols") {
-        // controls is a NAC; The control bar is in a XBL binding.
-        controlBar = controls.ownerDocument.getAnonymousElementByAttribute(
-          controls, "anonid", "controlBar");
-      } else {
-        // controls is a <div> that is the children of the UA Widget Shadow Root.
-        controlBar = controls.parentNode.getElementById("controlBar");
-      }
+      // controls is a <div> that is the children of the UA Widget Shadow Root.
+      controlBar = controls.parentNode.getElementById("controlBar");
       controlBarShown = controlBar ? !!controlBar.clientHeight : false;
     } else {
       // There is no controls element. This only happen to UA Widget because
       // it is created lazily.
       controlBarShown = false;
     }
 
     // Determine if we need to compute the display states of the cues. This could
--- a/dom/xbl/nsXBLService.cpp
+++ b/dom/xbl/nsXBLService.cpp
@@ -482,18 +482,16 @@ nsresult nsXBLService::LoadBindings(Elem
       IsSystemOrChromeURLPrincipal(aOriginPrincipal) && aElement->OwnerDoc() &&
       !aElement->OwnerDoc()->AllowXULXBL() &&
       !aURL->GetSpecOrDefault().EqualsLiteral(
           "chrome://global/content/xml/XMLPrettyPrint.xml#prettyprint")) {
     nsAtom* tag = aElement->NodeInfo()->NameAtom();
     MOZ_ASSERT(
         // datetimebox
         tag == nsGkAtoms::datetimebox ||
-            // videocontrols
-            tag == nsGkAtoms::videocontrols ||
             // pluginProblem
             tag == nsGkAtoms::embed || tag == nsGkAtoms::applet ||
             tag == nsGkAtoms::object ||
             // xbl-marquee
             tag == nsGkAtoms::marquee,
         "Unexpected XBL binding used in the content process");
   }
 #endif
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -657,17 +657,17 @@ nsresult nsXULElement::BindToTree(Docume
     nsAtom* tag = NodeInfo()->NameAtom();
     MOZ_ASSERT(
         // scrollbar parts
         tag == nsGkAtoms::scrollbar || tag == nsGkAtoms::scrollbarbutton ||
             tag == nsGkAtoms::scrollcorner || tag == nsGkAtoms::slider ||
             tag == nsGkAtoms::thumb ||
             // other
             tag == nsGkAtoms::datetimebox || tag == nsGkAtoms::resizer ||
-            tag == nsGkAtoms::label || tag == nsGkAtoms::videocontrols,
+            tag == nsGkAtoms::label,
         "Unexpected XUL element in non-XUL doc");
   }
 #endif
 
   if (doc && NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
     // Create our XUL key listener and hook it up.
     nsXBLService::AttachGlobalKeyHandler(this);
   }
--- a/layout/generic/nsFrameIdList.h
+++ b/layout/generic/nsFrameIdList.h
@@ -138,17 +138,17 @@ FRAME_ID(nsTableWrapperFrame, TableWrapp
 FRAME_ID(nsTableRowFrame, TableRow, NotLeaf)
 FRAME_ID(nsTableRowGroupFrame, TableRowGroup, NotLeaf)
 FRAME_ID(nsTextBoxFrame, LeafBox, Leaf)
 FRAME_ID(nsTextControlFrame, TextInput, Leaf)
 FRAME_ID(nsTextFrame, Text, Leaf)
 FRAME_ID(nsTitleBarFrame, Box, NotLeaf)
 FRAME_ID(nsTreeBodyFrame, LeafBox, Leaf)
 FRAME_ID(nsTreeColFrame, Box, NotLeaf)
-FRAME_ID(nsVideoFrame, HTMLVideo, DynamicLeaf)
+FRAME_ID(nsVideoFrame, HTMLVideo, NotLeaf)
 FRAME_ID(nsXULLabelFrame, XULLabel, NotLeaf)
 FRAME_ID(nsXULScrollFrame, Scroll, NotLeaf)
 FRAME_ID(ViewportFrame, Viewport, NotLeaf)
 
 // The following ABSTRACT_FRAME_IDs needs to come after the above
 // FRAME_IDs, because we have two separate enums, one that includes
 // only FRAME_IDs and another which includes both and we depend on
 // FRAME_IDs to have the same number in both.
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -124,65 +124,46 @@ nsresult nsVideoFrame::CreateAnonymousCo
     nsGenericHTMLElement* div =
         static_cast<nsGenericHTMLElement*>(mCaptionDiv.get());
     div->SetClassName(NS_LITERAL_STRING("caption-box"));
 
     if (!aElements.AppendElement(mCaptionDiv)) return NS_ERROR_OUT_OF_MEMORY;
     UpdateTextTrack();
   }
 
-  // Set up "videocontrols" XUL element which will be XBL-bound to the
-  // actual controls.
-  nodeInfo =
-      nodeInfoManager->GetNodeInfo(nsGkAtoms::videocontrols, nullptr,
-                                   kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
-  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_OUT_OF_MEMORY);
-
-  if (!nsContentUtils::IsUAWidgetEnabled()) {
-    NS_TrustedNewXULElement(getter_AddRefs(mVideoControls), nodeInfo.forget());
-    if (!aElements.AppendElement(mVideoControls)) return NS_ERROR_OUT_OF_MEMORY;
-  }
-
   return NS_OK;
 }
 
 void nsVideoFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
                                             uint32_t aFliter) {
   if (mPosterImage) {
     aElements.AppendElement(mPosterImage);
   }
 
-  if (mVideoControls) {
-    aElements.AppendElement(mVideoControls);
-  }
-
   if (mCaptionDiv) {
     aElements.AppendElement(mCaptionDiv);
   }
 }
 
 nsIContent* nsVideoFrame::GetVideoControls() {
-  if (mVideoControls) {
-    return mVideoControls;
+  if (!mContent->GetShadowRoot()) {
+    return nullptr;
   }
-  if (mContent->GetShadowRoot()) {
-    // The video controls <div> is the only child of the UA Widget Shadow Root
-    // if it is present. It is only lazily inserted into the DOM when
-    // the controls attribute is set.
-    MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget());
-    MOZ_ASSERT(1 >= mContent->GetShadowRoot()->GetChildCount());
-    return mContent->GetShadowRoot()->GetFirstChild();
-  }
-  return nullptr;
+
+  // The video controls <div> is the only child of the UA Widget Shadow Root
+  // if it is present. It is only lazily inserted into the DOM when
+  // the controls attribute is set.
+  MOZ_ASSERT(mContent->GetShadowRoot()->IsUAWidget());
+  MOZ_ASSERT(1 >= mContent->GetShadowRoot()->GetChildCount());
+  return mContent->GetShadowRoot()->GetFirstChild();
 }
 
 void nsVideoFrame::DestroyFrom(nsIFrame* aDestructRoot,
                                PostDestroyData& aPostDestroyData) {
   aPostDestroyData.AddAnonymousContent(mCaptionDiv.forget());
-  aPostDestroyData.AddAnonymousContent(mVideoControls.forget());
   aPostDestroyData.AddAnonymousContent(mPosterImage.forget());
   nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
 }
 
 already_AddRefed<Layer> nsVideoFrame::BuildLayer(
     nsDisplayListBuilder* aBuilder, LayerManager* aManager,
     nsDisplayItem* aItem,
     const ContainerLayerParameters& aContainerParameters) {
@@ -400,31 +381,16 @@ void nsVideoFrame::Reflow(nsPresContext*
 
   NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsVideoFrame::Reflow: size=%d,%d",
                                         aMetrics.Width(), aMetrics.Height()));
 
   MOZ_ASSERT(aStatus.IsEmpty(), "This type of frame can't be split.");
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
 }
 
-/**
- * nsVideoFrame should be a non-leaf frame when UA Widget is enabled,
- * so the videocontrols container element inserted under the Shadow Root can be
- * picked up. No frames will be generated from elements from the web content,
- * given that they have been replaced by the Shadow Root without and <slots>
- * element in the DOM tree.
- *
- * When the UA Widget is disabled, i.e. the videocontrols is bound as anonymous
- * content with XBL, nsVideoFrame has to be a leaf so no frames from web content
- * element will be generated.
- */
-bool nsVideoFrame::IsLeafDynamic() const {
-  return !nsContentUtils::IsUAWidgetEnabled();
-}
-
 class nsDisplayVideo : public nsDisplayItem {
  public:
   nsDisplayVideo(nsDisplayListBuilder* aBuilder, nsVideoFrame* aFrame)
       : nsDisplayItem(aBuilder, aFrame) {
     MOZ_COUNT_CTOR(nsDisplayVideo);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayVideo() { MOZ_COUNT_DTOR(nsDisplayVideo); }
--- a/layout/generic/nsVideoFrame.h
+++ b/layout/generic/nsVideoFrame.h
@@ -65,18 +65,16 @@ class nsVideoFrame final : public nsCont
   nscoord GetPrefISize(gfxContext* aRenderingContext) override;
   void DestroyFrom(nsIFrame* aDestructRoot,
                    PostDestroyData& aPostDestroyData) override;
 
   void Reflow(nsPresContext* aPresContext, ReflowOutput& aDesiredSize,
               const ReflowInput& aReflowInput,
               nsReflowStatus& aStatus) override;
 
-  bool IsLeafDynamic() const override;
-
 #ifdef ACCESSIBILITY
   mozilla::a11y::AccType AccessibleType() override;
 #endif
 
   bool IsFrameOfType(uint32_t aFlags) const override {
     return nsSplittableFrame::IsFrameOfType(
         aFlags & ~(nsIFrame::eReplaced | nsIFrame::eReplacedSizing));
   }
@@ -118,19 +116,16 @@ class nsVideoFrame final : public nsCont
   // elements, not for frames for audio elements.
   void UpdatePosterSource(bool aNotify);
 
   // Notify the mediaElement that the mCaptionDiv was created.
   void UpdateTextTrack();
 
   virtual ~nsVideoFrame();
 
-  // Anonymous child which is bound via XBL to the video controls.
-  RefPtr<mozilla::dom::Element> mVideoControls;
-
   // Anonymous child which is the image element of the poster frame.
   RefPtr<mozilla::dom::Element> mPosterImage;
 
   // Anonymous child which is the text track caption display div.
   nsCOMPtr<nsIContent> mCaptionDiv;
 };
 
 #endif /* nsVideoFrame_h___ */
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1184,22 +1184,16 @@ fails-if(usesRepeatResampling) fails-if(
 fails-if(usesRepeatResampling) fails-if(webrender&&!winWidget) == 446100-1e.html about:blank
 == 446100-1f.html about:blank
 fails-if(usesRepeatResampling) fails-if(Android) fails-if(webrender&&!winWidget) == 446100-1g.html about:blank
 == 446100-1h.html about:blank
 == 447749-1.html 447749-1-ref.html
 fuzzy(0-127,0-2) == 448193.html 448193-ref.html
 != 449149-1a.html about:blank
 != 449149-1b.html about:blank
-# Test again with original XBL bindings
-test-pref(dom.ua_widget.enabled,false) != 449149-1a.html about:blank
-test-pref(dom.ua_widget.enabled,false) != 449149-1b.html about:blank
-# Retry the above with XBL scopes
-test-pref(dom.ua_widget.enabled,false) test-pref(dom.use_xbl_scopes_for_remote_xul,true) != 449149-1a.html about:blank
-test-pref(dom.ua_widget.enabled,false) test-pref(dom.use_xbl_scopes_for_remote_xul,true) != 449149-1b.html about:blank
 == 449149-2.html 449149-2-ref.html
 == 449171-1.html 449171-ref.html
 == 449362-1.html 449362-1-ref.html
 fuzzy-if(webrender,0-4,0-361) == 449519-1.html 449519-1-ref.html
 == 450670-1.html 450670-1-ref.html
 == 451168-1.html 451168-1-ref.html
 == 451876-1.html 451876-1-ref.html
 == 451876-2.html 451876-2-ref.html
--- a/layout/style/res/html.css
+++ b/layout/style/res/html.css
@@ -281,29 +281,29 @@ table[align="right"] {
 
 /* border collapse rules */
 
   /* Set hidden if we have 'frame' or 'rules' attribute.
      Set it on all sides when we do so there's more consistency
      in what authors should expect */
 
   /* Put this first so 'border' and 'frame' rules can override it. */
-table[rules] { 
+table[rules] {
   border-width: thin;
   border-style: hidden;
 }
 
   /* 'border' before 'frame' so 'frame' overrides
       A border with a given value should, of course, pass that value
       as the border-width in pixels -> attr mapping */
 
   /* :-moz-table-border-nonzero is like [border]:not([border="0"]) except it
      also checks for other zero-like values according to HTML attribute
      parsing rules */
-table:-moz-table-border-nonzero { 
+table:-moz-table-border-nonzero {
   border-width: thin;
   border-style: outset;
 }
 
 table[frame] {
   border: thin hidden;
 }
 
@@ -313,17 +313,17 @@ table[frame="above"]  { border-style: ou
 table[frame="below"]  { border-style: hidden hidden outset hidden; }
 table[frame="lhs"]    { border-style: hidden hidden hidden outset; }
 table[frame="rhs"]    { border-style: hidden outset hidden hidden; }
 table[frame="hsides"] { border-style: outset hidden; }
 table[frame="vsides"] { border-style: hidden outset; }
 table[frame="box"],
 table[frame="border"] { border-style: outset; }
 
- 
+
 /* Internal Table Borders */
 
   /* 'border' cell borders first */
 
 table:-moz-table-border-nonzero > * > tr > td,
 table:-moz-table-border-nonzero > * > tr > th,
 table:-moz-table-border-nonzero > * > td,
 table:-moz-table-border-nonzero > * > th,
@@ -334,17 +334,17 @@ table:-moz-table-border-nonzero > th
   border-style: inset;
 }
 
 /* collapse only if rules are really specified */
 table[rules]:not([rules="none"]):not([rules=""]) {
   border-collapse: collapse;
 }
 
-/* only specified rules override 'border' settings  
+/* only specified rules override 'border' settings
   (increased specificity to achieve this) */
 table[rules]:not([rules=""])> tr > td,
 table[rules]:not([rules=""])> * > tr > td,
 table[rules]:not([rules=""])> tr > th,
 table[rules]:not([rules=""])> * > tr > th,
 table[rules]:not([rules=""])> td,
 table[rules]:not([rules=""])> th
 {
@@ -364,17 +364,17 @@ table[rules][rules="none"] > th
   border-style: none;
 }
 
 table[rules][rules="all"] > tr > td,
 table[rules][rules="all"] > * > tr > td,
 table[rules][rules="all"] > tr > th,
 table[rules][rules="all"] > * > tr > th,
 table[rules][rules="all"] > td,
-table[rules][rules="all"] > th 
+table[rules][rules="all"] > th
 {
   border-width: thin;
   border-style: solid;
 }
 
 table[rules][rules="rows"] > tr,
 table[rules][rules="rows"] > * > tr {
   border-block-start-width: thin;
@@ -403,19 +403,19 @@ table[rules][rules="groups"] > colgroup 
 table[rules][rules="groups"] > tfoot,
 table[rules][rules="groups"] > thead,
 table[rules][rules="groups"] > tbody {
   border-block-start-width: thin;
   border-block-end-width: thin;
   border-block-start-style: solid;
   border-block-start-style: solid;
 }
-  
-  
-/* caption inherits from table not table-outer */  
+
+
+/* caption inherits from table not table-outer */
 caption {
   display: table-caption;
   text-align: center;
 }
 
 table[align="center"] > caption {
   margin-inline-start: auto;
   margin-inline-end: auto;
@@ -463,20 +463,20 @@ tfoot {
   vertical-align: middle;
 }
 
 /* for XHTML tables without tbody */
 table > tr {
   vertical-align: middle;
 }
 
-td { 
+td {
   display: table-cell;
   vertical-align: inherit;
-  text-align: unset; 
+  text-align: unset;
   padding: 1px;
 }
 
 th {
   display: table-cell;
   vertical-align: inherit;
   font-weight: bold;
   padding: 1px;
@@ -676,17 +676,17 @@ img[usemap], object[usemap] {
 frameset {
   display: block ! important;
   overflow: -moz-hidden-unscrollable;
   position: static ! important;
   float: none ! important;
   border: none ! important;
 }
 
-link { 
+link {
   display: none;
 }
 
 frame {
   border-radius: 0 ! important;
 }
 
 iframe {
@@ -727,28 +727,16 @@ area {
 
 iframe:fullscreen {
   /* iframes in full-screen mode don't show a border. */
   border: none !important;
   padding: unset !important;
 }
 
 /* media elements */
-video > xul|videocontrols, audio > xul|videocontrols {
-  display: -moz-box;
-  -moz-box-orient: vertical;
-  -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#videoControls");
-}
-
-video:not([controls]) > xul|videocontrols,
-audio:not([controls]) > xul|videocontrols {
-  visibility: hidden;
-  -moz-binding: none;
-}
-
 video {
   object-fit: contain;
 }
 
 video > img:-moz-native-anonymous {
   /* Video poster images should render with the video element's "object-fit" &
      "object-position" properties */
   object-fit: inherit !important;
--- a/mobile/android/tests/browser/chrome/test_mozAutoplayMediaBlocked.html
+++ b/mobile/android/tests/browser/chrome/test_mozAutoplayMediaBlocked.html
@@ -33,18 +33,16 @@ SimpleTest.requestLongerTimeout(2);
   gChromeWin = Services.wm.getMostRecentWindow("navigator:browser");
   gBrowserApp = gChromeWin.BrowserApp;
 
   Services.prefs.setIntPref("media.autoplay.default", 1 /* BLOCKED */);
   Services.prefs.setBoolPref("media.autoplay.enabled.user-gestures-needed", true);
 })();
 
 add_task(async function test_UAWidgetMozAutoplayMediaBlocked() {
-  Services.prefs.setBoolPref("dom.ua_widget.enabled", true);
-
   info("- open a new tab -");
   let tab = gBrowserApp.addTab(URL);
   let browser = tab.browser;
 
   const mediaBlockedPromise = promiseTabEvent(browser, "MozAutoplayMediaBlocked");
 
   info("- wait for loading tab's content -");
   await promiseBrowserEvent(browser, "load");
@@ -58,44 +56,16 @@ add_task(async function test_UAWidgetMoz
 
   let button = video.openOrClosedShadowRoot.getElementById("clickToPlay");
   ok(!button.hidden, "Click to play button is not hidden");
 
   info("- remove tab -");
   gBrowserApp.closeTab(tab);
 });
 
-add_task(async function test_XBLBindingMozAutoplayMediaBlocked() {
-  Services.prefs.setBoolPref("dom.ua_widget.enabled", false);
-
-  info("- open a new tab -");
-  let tab = gBrowserApp.addTab(URL);
-  let browser = tab.browser;
-
-  const mediaBlockedPromise = promiseTabEvent(browser, "MozAutoplayMediaBlocked");
-
-  info("- wait for loading tab's content -");
-  await promiseBrowserEvent(browser, "load");
-
-  info("- wait for 'MozAutoplayMediaBlocked' event -");
-  await mediaBlockedPromise;
-  ok(true, "got `MozAutoplayMediaBlocked` event");
-
-  let doc = browser.contentWindow.document;
-  let video = doc.getElementById("testAudio");
-
-  let kids = InspectorUtils.getChildrenForNode(video, true);
-  let videocontrols = kids[1];
-  let button = doc.getAnonymousElementByAttribute(videocontrols, "anonid", "clickToPlay");
-  ok(!button.hidden, "Click to play button is not hidden");
-
-  info("- remove tab -");
-  gBrowserApp.closeTab(tab);
-});
-
   </script>
 </head>
 <body>
 
 </div>
 <pre id="test">
 </pre>
 </body>
--- a/mobile/android/themes/geckoview/content.css
+++ b/mobile/android/themes/geckoview/content.css
@@ -260,22 +260,16 @@ button:disabled:active {
 select:disabled > button {
   opacity: 0.6;
   padding-inline-start: 7px;
   padding-inline-end: 7px;
   padding-block-start: 1px;
   padding-block-end: 1px;
 }
 
-/* display click to play when autoplay is blocked for videos */
-video:not([controls]) > xul|videocontrols {
-  visibility: visible;
-  -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#noControls");
-}
-
 *:any-link:active,
 *[role=button]:active,
 button:not(:disabled):active,
 input:not(:-moz-any([type="checkbox"], [type="radio"])):not(:focus):not(:disabled):active,
 select:not(:disabled):active,
 textarea:not(:focus):not(:disabled):active,
 option:active,
 label:active,
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -80,17 +80,16 @@ toolkit.jar:
    content/global/bindings/spinner.js          (widgets/spinner.js)
    content/global/bindings/tabbox.xml          (widgets/tabbox.xml)
    content/global/bindings/text.xml            (widgets/text.xml)
 *  content/global/bindings/textbox.xml         (widgets/textbox.xml)
    content/global/bindings/timekeeper.js       (widgets/timekeeper.js)
    content/global/bindings/timepicker.js       (widgets/timepicker.js)
    content/global/bindings/toolbarbutton.xml   (widgets/toolbarbutton.xml)
    content/global/bindings/tree.xml            (widgets/tree.xml)
-   content/global/bindings/videocontrols.xml   (widgets/videocontrols.xml)
 *  content/global/bindings/wizard.xml          (widgets/wizard.xml)
    content/global/elements/browser-custom-element.js          (widgets/browser-custom-element.js)
    content/global/elements/datetimebox.js      (widgets/datetimebox.js)
    content/global/elements/findbar.js          (widgets/findbar.js)
    content/global/elements/editor.js           (widgets/editor.js)
    content/global/elements/general.js          (widgets/general.js)
    content/global/elements/notificationbox.js  (widgets/notificationbox.js)
    content/global/elements/pluginProblem.js    (widgets/pluginProblem.js)
--- a/toolkit/content/tests/moz.build
+++ b/toolkit/content/tests/moz.build
@@ -9,16 +9,14 @@ XPCSHELL_TESTS_MANIFESTS += ['unit/xpcsh
 BROWSER_CHROME_MANIFESTS += [
     'browser/browser.ini',
     'browser/xbl/browser.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += [
     'chrome/chrome.ini',
     'widgets/chrome.ini',
-    'widgets/xbl/chrome.ini',
 ]
 
 MOCHITEST_MANIFESTS += [
     'mochitest/mochitest.ini',
     'widgets/mochitest.ini',
-    'widgets/xbl/mochitest.ini',
 ]
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/chrome.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-[DEFAULT]
-prefs =
-  dom.ua_widget.enabled=false
-skip-if = os == 'android'
-support-files =
-  ../seek_with_sound.ogg
-
-[test_videocontrols_onclickplay.html]
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/head.js
+++ /dev/null
@@ -1,43 +0,0 @@
-"use strict";
-
-const InspectorUtils = SpecialPowers.InspectorUtils;
-
-var tests = [];
-
-function waitForCondition(condition, nextTest, errorMsg) {
-  var tries = 0;
-  var interval = setInterval(function() {
-    if (tries >= 30) {
-      ok(false, errorMsg);
-      moveOn();
-    }
-    var conditionPassed;
-    try {
-      conditionPassed = condition();
-    } catch (e) {
-      ok(false, e + "\n" + e.stack);
-      conditionPassed = false;
-    }
-    if (conditionPassed) {
-      moveOn();
-    }
-    tries++;
-  }, 100);
-  var moveOn = function() { clearInterval(interval); nextTest(); };
-}
-
-function getAnonElementWithinVideoByAttribute(video, aName, aValue) {
-  // <videocontrols> is the second anonymous child node of <video>, but
-  // the first child node of <audio>.
-  const videoControlIndex = video.nodeName == "VIDEO" ? 1 : 0;
-  const videoControl = InspectorUtils.getChildrenForNode(video, true)[videoControlIndex];
-
-  return videoControl.ownerDocument
-    .getAnonymousElementByAttribute(videoControl, aName, aValue);
-}
-
-function executeTests() {
-  return tests
-    .map(fn => () => new Promise(fn))
-    .reduce((promise, task) => promise.then(task), Promise.resolve());
-}
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/mochitest.ini
+++ /dev/null
@@ -1,47 +0,0 @@
-[DEFAULT]
-prefs =
-  dom.ua_widget.enabled=false
-support-files =
-  ../audio.wav
-  ../audio.ogg
-  ../file_videocontrols_jsdisabled.html
-  ../seek_with_sound.ogg
-  ../video.ogg
-  head.js
-  ../videocontrols_direction-1-ref.html
-  ../videocontrols_direction-1a.html
-  ../videocontrols_direction-1b.html
-  ../videocontrols_direction-1c.html
-  ../videocontrols_direction-1d.html
-  ../videocontrols_direction-1e.html
-  ../videocontrols_direction-2-ref.html
-  ../videocontrols_direction-2a.html
-  ../videocontrols_direction-2b.html
-  ../videocontrols_direction-2c.html
-  ../videocontrols_direction-2d.html
-  ../videocontrols_direction-2e.html
-  ../videocontrols_direction_test.js
-  ../videomask.css
-
-[test_audiocontrols_dimensions.html]
-[test_videocontrols.html]
-tags = fullscreen
-skip-if = toolkit == 'android' || (verify && debug && (os == 'linux')) || (os == 'linux') #TIMED_OUT, Bug 1511256
-[test_videocontrols_keyhandler.html]
-skip-if = (toolkit == 'android') || (os == 'linux') #Bug 1366957
-[test_videocontrols_vtt.html]
-[test_videocontrols_iframe_fullscreen.html]
-[test_videocontrols_size.html]
-[test_videocontrols_audio.html]
-[test_videocontrols_audio_direction.html]
-[test_videocontrols_jsdisabled.html]
-skip-if = toolkit == 'android' # bug 1272646
-[test_videocontrols_standalone.html]
-skip-if = toolkit == 'android' # bug 1075573
-[test_videocontrols_video_direction.html]
-skip-if = os == 'win'
-[test_videocontrols_video_noaudio.html]
-[test_bug898940.html]
-[test_videocontrols_error.html]
-[test_videocontrols_orientation.html]
-run-if = toolkit == 'android'
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_audiocontrols_dimensions.html
+++ /dev/null
@@ -1,67 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Audio controls test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <audio id="audio" controls preload="auto"></audio>
-</div>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-  const audio = document.getElementById("audio");
-  const controlBar = getAnonElementWithinVideoByAttribute(audio, "anonid", "controlBar");
-
-  add_task(async function setup() {
-    await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
-    await new Promise(resolve => {
-      audio.addEventListener("loadedmetadata", resolve, {once: true});
-      audio.src = "audio.wav";
-    });
-  });
-
-  add_task(async function check_audio_height() {
-    is(audio.clientHeight, 40, "checking height of audio element");
-  });
-
-  add_task(async function check_controlbar_width() {
-    const originalControlBarWidth = controlBar.clientWidth;
-
-    isnot(originalControlBarWidth, 400, "the default audio width is not 400px");
-
-    audio.style.width = "400px";
-    audio.offsetWidth; // force reflow
-
-    isnot(controlBar.clientWidth, originalControlBarWidth, "new width should differ from the origianl width");
-    is(controlBar.clientWidth, 400, "controlbar's width should grow with audio width");
-  });
-
-  add_task(function check_audio_height_construction_sync() {
-    let el = new Audio();
-    el.src = "audio.wav";
-    el.controls = true;
-    document.body.appendChild(el);
-    is(el.clientHeight, 40, "Height of audio element with controls");
-    document.body.removeChild(el);
-  });
-
-  add_task(function check_audio_height_add_control_sync() {
-    let el = new Audio();
-    el.src = "audio.wav";
-    document.body.appendChild(el);
-    is(el.clientHeight, 0, "Height of audio element without controls");
-    el.controls = true;
-    is(el.clientHeight, 40, "Height of audio element with controls");
-    document.body.removeChild(el);
-  });
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_bug898940.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test that an audio element that's already playing when controls are attached displays the controls</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <audio id="audio" controls src="audio.ogg"></audio>
-</div>
-
-<pre id="test">
-<script class="testbody">
-  var audio = document.getElementById("audio");
-  audio.play();
-  audio.ontimeupdate = function doTest() {
-    ok(audio.getBoundingClientRect().height > 0,
-       "checking audio element height is greater than zero");
-    audio.ontimeupdate = null;
-    SimpleTest.finish();
-  };
-
-  SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols.html
+++ /dev/null
@@ -1,447 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video width="320" height="240" id="video" controls mozNoDynamicControls preload="auto"></video>
-</div>
-
-<div id="host"></div>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/*
- * Positions of the  UI elements, relative to the upper-left corner of the
- * <video> box.
- */
-const videoWidth = 320;
-const videoHeight = 240;
-const videoDuration = 3.8329999446868896;
-
-const controlBarMargin = 9;
-
-const playButtonWidth = 30;
-const playButtonHeight = 40;
-const muteButtonWidth = 30;
-const muteButtonHeight = 40;
-const positionAndDurationWidth = 75;
-const fullscreenButtonWidth = 30;
-const fullscreenButtonHeight = 40;
-const volumeSliderWidth = 48;
-const volumeSliderMarginStart = 4;
-const volumeSliderMarginEnd = 6;
-const scrubberMargin = 9;
-const scrubberWidth = videoWidth - controlBarMargin - playButtonWidth - scrubberMargin * 2 - positionAndDurationWidth - muteButtonWidth - volumeSliderMarginStart - volumeSliderWidth - volumeSliderMarginEnd - fullscreenButtonWidth - controlBarMargin;
-const scrubberHeight = 40;
-
-// Play button is on the bottom-left
-const playButtonCenterX = 0 + Math.round(playButtonWidth / 2);
-const playButtonCenterY = videoHeight - Math.round(playButtonHeight / 2);
-// Mute button is on the bottom-right before the full screen button and volume slider
-const muteButtonCenterX = videoWidth - Math.round(muteButtonWidth / 2) - volumeSliderWidth - fullscreenButtonWidth - controlBarMargin;
-const muteButtonCenterY = videoHeight - Math.round(muteButtonHeight / 2);
-// Fullscreen button is on the bottom-right at the far end
-const fullscreenButtonCenterX = videoWidth - Math.round(fullscreenButtonWidth / 2) - controlBarMargin;
-const fullscreenButtonCenterY = videoHeight - Math.round(fullscreenButtonHeight / 2);
-// Scrubber bar is between the play and mute buttons. We don't need it's
-// X center, just the offset of its box.
-const scrubberOffsetX = controlBarMargin + playButtonWidth + scrubberMargin;
-const scrubberCenterY = videoHeight - Math.round(scrubberHeight / 2);
-
-const screenWidth = window.screen.width;
-const screenHeight = window.screen.height;
-
-const video = document.getElementById("video");
-
-let expectingEvents;
-let expectingEventPromise;
-
-async function isMuteButtonMuted() {
-  const muteButton = getAnonElementWithinVideoByAttribute(video, "anonid", "muteButton");
-  await new Promise(SimpleTest.executeSoon);
-  return muteButton.getAttribute("muted") === "true";
-}
-
-async function isVolumeSliderShowingCorrectVolume(expectedVolume) {
-  const volumeControl = getAnonElementWithinVideoByAttribute(video, "anonid", "volumeControl");
-  await new Promise(SimpleTest.executeSoon);
-  is(+volumeControl.value, expectedVolume * 100, "volume slider should match expected volume");
-}
-
-function forceReframe() {
-  // Setting display then getting the bounding rect to force a frame
-  // reconstruction on the video element.
-  video.style.display = "block";
-  video.getBoundingClientRect();
-  video.style.display = "";
-  video.getBoundingClientRect();
-}
-
-function verifyExpectedEvent(event) {
-  const checkingEvent = expectingEvents.shift();
-
-  if (event.type == checkingEvent) {
-    ok(true, "checking event type: " + checkingEvent);
-  } else {
-    expectingEventPromise.reject(new Error(`Got event: ${event.type}, expected: ${checkingEvent}`));
-  }
-
-  if (expectingEvents.length == 0) {
-    SimpleTest.executeSoon(expectingEventPromise.resolve);
-  }
-}
-
-async function waitForEvent(...eventTypes) {
-  expectingEvents = eventTypes;
-
-  await new Promise((resolve, reject) => expectingEventPromise = {resolve, reject}).catch(e => {
-    // Throw error here to get the caller in error stack.
-    ok(false, e);
-  });
-}
-
-add_task(async function setup() {
-  await SpecialPowers.pushPrefEnv({
-      "set": [
-        ["media.cache_size", 40000],
-        ["full-screen-api.enabled", true],
-        ["full-screen-api.allow-trusted-requests-only", false],
-        ["full-screen-api.transition-duration.enter", "0 0"],
-        ["full-screen-api.transition-duration.leave", "0 0"],
-      ]});
-  await new Promise(resolve => {
-    video.addEventListener("canplaythrough", resolve, {once: true});
-    video.src = "seek_with_sound.ogg";
-  });
-
-  video.addEventListener("play", verifyExpectedEvent);
-  video.addEventListener("pause", verifyExpectedEvent);
-  video.addEventListener("volumechange", verifyExpectedEvent);
-  video.addEventListener("seeking", verifyExpectedEvent);
-  video.addEventListener("seeked", verifyExpectedEvent);
-  document.addEventListener("mozfullscreenchange", verifyExpectedEvent);
-
-  ["mousedown", "mouseup", "dblclick", "click"]
-    .forEach((eventType) => {
-      window.addEventListener(eventType, (evt) => {
-        // Prevent default action of leaked events and make the tests fail.
-        evt.preventDefault();
-        ok(false, "Event " + eventType + " in videocontrol should not leak to content;" +
-          "the event was dispatched from the " + evt.target.tagName.toLowerCase() + " element.");
-      });
-    });
-
-  // Check initial state upon load
-  is(video.paused, true, "checking video play state");
-  is(video.muted, false, "checking video mute state");
-});
-
-add_task(async function click_playbutton() {
-  synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
-  await waitForEvent("play");
-  is(video.paused, false, "checking video play state");
-  is(video.muted, false, "checking video mute state");
-});
-
-add_task(async function click_pausebutton() {
-  synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
-  await waitForEvent("pause");
-  is(video.paused, true, "checking video play state");
-  is(video.muted, false, "checking video mute state");
-});
-
-add_task(async function mute_volume() {
-  synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
-  await waitForEvent("volumechange");
-  is(video.paused, true, "checking video play state");
-  is(video.muted, true, "checking video mute state");
-});
-
-add_task(async function unmute_volume() {
-  synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
-  await waitForEvent("volumechange");
-  is(video.paused, true, "checking video play state");
-  is(video.muted, false, "checking video mute state");
-});
-
-/*
- * Bug 470596: Make sure that having CSS border or padding doesn't
- * break the controls (though it should move them)
- */
-add_task(async function styled_video() {
-  video.style.border = "medium solid purple";
-  video.style.borderWidth = "30px 40px 50px 60px";
-  video.style.padding = "10px 20px 30px 40px";
-  // totals: top: 40px, right: 60px, bottom: 80px, left: 100px
-
-  // Click the play button
-  synthesizeMouse(video, 100 + playButtonCenterX, 40 + playButtonCenterY, { });
-  await waitForEvent("play");
-  is(video.paused, false, "checking video play state");
-  is(video.muted, false, "checking video mute state");
-
-  // Pause the video
-  video.pause();
-  await waitForEvent("pause");
-  is(video.paused, true, "checking video play state");
-  is(video.muted, false, "checking video mute state");
-
-  // Click the mute button
-  synthesizeMouse(video, 100 + muteButtonCenterX, 40 + muteButtonCenterY, {});
-  await waitForEvent("volumechange");
-  is(video.paused, true, "checking video play state");
-  is(video.muted, true, "checking video mute state");
-
-  // Clear the style set
-  video.style.border = "";
-  video.style.borderWidth = "";
-  video.style.padding = "";
-
-  // Unmute the video
-  video.muted = false;
-  await waitForEvent("volumechange");
-  is(video.paused, true, "checking video play state");
-  is(video.muted, false, "checking video mute state");
-});
-
-/*
- * Previous tests have moved playback postion, reset it to 0.
- */
-add_task(async function reset_currentTime() {
-  ok(true, "video position is at " + video.currentTime);
-  video.currentTime = 0.0;
-  await waitForEvent("seeking", "seeked");
-  // Bug 477434 -- sometimes we get 0.098999 here instead of 0!
-  // is(video.currentTime, 0.0, "checking playback position");
-  ok(true, "video position is at " + video.currentTime);
-});
-
-/*
- * Drag the slider's thumb to the halfway point with the mouse.
- */
-add_task(async function drag_slider() {
-  const beginDragX = scrubberOffsetX;
-  const endDragX = scrubberOffsetX + (scrubberWidth / 2);
-  const expectedTime = videoDuration / 2;
-
-  function mousemoved(evt) {
-    ok(false, "Mousemove event should not be handled by content while dragging; " +
-      "the event was dispatched from the " + evt.target.tagName.toLowerCase() + " element.");
-  }
-
-  window.addEventListener("mousemove", mousemoved);
-
-  synthesizeMouse(video, beginDragX, scrubberCenterY, {type: "mousedown", button: 0});
-  synthesizeMouse(video, endDragX, scrubberCenterY, {type: "mousemove", button: 0});
-  synthesizeMouse(video, endDragX, scrubberCenterY, {type: "mouseup",   button: 0});
-  await waitForEvent("seeking", "seeked");
-  ok(true, "video position is at " + video.currentTime);
-  // The width of srubber is not equal in every platform as we use system default font
-  // in duration and position box. We can not precisely drag to expected position, so
-  // we just make sure the difference is within 10% of video duration.
-  ok(Math.abs(video.currentTime - expectedTime) < videoDuration / 10, "checking expected playback position");
-
-  window.removeEventListener("mousemove", mousemoved);
-});
-
-/*
- * Click the slider at the 1/4 point with the mouse (jump backwards)
- */
-add_task(async function click_slider() {
-  synthesizeMouse(video, scrubberOffsetX + (scrubberWidth / 4), scrubberCenterY, {});
-  await waitForEvent("seeking", "seeked");
-  ok(true, "video position is at " + video.currentTime);
-  // The scrubber currently just jumps towards the nearest pageIncrement point, not
-  // precisely to the point clicked. So, expectedTime isn't (videoDuration / 4).
-  // We should end up at 1.733, but sometimes we end up at 1.498. I guess
-  // it's timing depending if the <scale> things it's click-and-hold, or a
-  // single click. So, just make sure we end up less that the previous
-  // position.
-  const lastPosition = (videoDuration / 2) - 0.1;
-  ok(video.currentTime < lastPosition, "checking expected playback position");
-
-  // Set volume to 0.1 so one down arrow hit will decrease it to 0.
-  video.volume = 0.1;
-  await waitForEvent("volumechange");
-  is(video.volume, 0.1, "Volume should be set.");
-  ok(!video.muted, "Video is not muted.");
-});
-
-// See bug 694696.
-add_task(async function change_volume() {
-  video.focus();
-
-  synthesizeKey("KEY_ArrowDown");
-  await waitForEvent("volumechange");
-  is(video.volume, 0, "Volume should be 0.");
-  ok(!video.muted, "Video is not muted.");
-  ok(await isMuteButtonMuted(), "Mute button says it's muted");
-
-  synthesizeKey("KEY_ArrowUp");
-  await waitForEvent("volumechange");
-  is(video.volume, 0.1, "Volume is increased.");
-  ok(!video.muted, "Video is not muted.");
-  ok(!(await isMuteButtonMuted()), "Mute button says it's not muted");
-
-  synthesizeKey("KEY_ArrowDown");
-  await waitForEvent("volumechange");
-  is(video.volume, 0, "Volume should be 0.");
-  ok(!video.muted, "Video is not muted.");
-  ok(await isMuteButtonMuted(), "Mute button says it's muted");
-
-  synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
-  await waitForEvent("volumechange");
-  is(video.volume, 0.5, "Volume should be 0.5.");
-  ok(!video.muted, "Video is not muted.");
-
-  synthesizeKey("KEY_ArrowUp");
-  await waitForEvent("volumechange");
-  is(video.volume, 0.6, "Volume should be 0.6.");
-  ok(!video.muted, "Video is not muted.");
-
-  synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
-  await waitForEvent("volumechange");
-  is(video.volume, 0.6, "Volume should be 0.6.");
-  ok(video.muted, "Video is muted.");
-  ok(await isMuteButtonMuted(), "Mute button says it's muted");
-
-  synthesizeMouse(video, muteButtonCenterX, muteButtonCenterY, {});
-  await waitForEvent("volumechange");
-  is(video.volume, 0.6, "Volume should be 0.6.");
-  ok(!video.muted, "Video is not muted.");
-  ok(!(await isMuteButtonMuted()), "Mute button says it's not muted");
-
-  synthesizeMouse(video, fullscreenButtonCenterX, fullscreenButtonCenterY, {});
-  await waitForEvent("mozfullscreenchange");
-  is(video.volume, 0.6, "Volume should still be 0.6");
-  await isVolumeSliderShowingCorrectVolume(video.volume);
-
-  synthesizeKey("KEY_Escape");
-  await waitForEvent("mozfullscreenchange");
-  is(video.volume, 0.6, "Volume should still be 0.6");
-  await isVolumeSliderShowingCorrectVolume(video.volume);
-  forceReframe();
-
-  video.focus();
-  synthesizeKey("KEY_ArrowDown");
-  await waitForEvent("volumechange");
-  is(video.volume, 0.5, "Volume should be decreased by 0.1");
-  await isVolumeSliderShowingCorrectVolume(video.volume);
-});
-
-add_task(async function whitespace_pause_video() {
-  synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
-  await waitForEvent("play");
-
-  video.focus();
-  sendString(" ");
-  await waitForEvent("pause");
-
-  synthesizeMouse(video, playButtonCenterX, playButtonCenterY, {});
-  await waitForEvent("play");
-});
-
-/*
- * Bug 1352724: Click and hold on timeline should pause video immediately.
- */
-add_task(async function click_and_hold_slider() {
-  synthesizeMouse(video, scrubberOffsetX + 10, scrubberCenterY, {type: "mousedown", button: 0});
-  await waitForEvent("pause", "seeking", "seeked");
-
-  synthesizeMouse(video, scrubberOffsetX + 10, scrubberCenterY, {});
-  await waitForEvent("play");
-});
-
-/*
- * Bug 1402877: Don't let click event dispatch through media controls to video element.
- */
-add_task(async function click_event_dispatch() {
-  const clientScriptClickHandler = (e) => {
-    ok(false, "Should not receive the event");
-  };
-  video.addEventListener("click", clientScriptClickHandler);
-
-  video.pause();
-  await waitForEvent("pause");
-  video.currentTime = 0.0;
-  await waitForEvent("seeking", "seeked");
-  is(video.paused, true, "checking video play state");
-  synthesizeMouse(video, scrubberOffsetX + 10, scrubberCenterY, {});
-  await waitForEvent("seeking", "seeked");
-
-  video.removeEventListener("click", clientScriptClickHandler);
-});
-
-// Bug 1367194: Always ensure video is paused before finishing the test.
-add_task(async function ensure_video_pause() {
-  if (!video.paused) {
-    video.pause();
-    await waitForEvent("pause");
-  }
-});
-
-// Bug 1452342: Make sure the cursor hides and shows on full screen mode.
-add_task(async function ensure_fullscreen_cursor() {
-  video.removeAttribute("mozNoDynamicControls");
-  video.play();
-  await waitForEvent("play");
-
-  video.mozRequestFullScreen();
-  await waitForEvent("mozfullscreenchange");
-
-  const controlsSpacer = getAnonElementWithinVideoByAttribute(video, "anonid", "controlsSpacer");
-  is(controlsSpacer.hasAttribute("hideCursor"), true, "Cursor is hidden");
-
-  // Wiggle the mouse a bit
-  synthesizeMouse(video, playButtonCenterX, playButtonCenterY, { type: "mousemove" });
-
-  is(controlsSpacer.hasAttribute("hideCursor"), false, "Cursor is shown");
-
-  // Restore
-  video.setAttribute("mozNoDynamicControls", "");
-  document.mozCancelFullScreen();
-  await waitForEvent("mozfullscreenchange");
-  if (!video.paused) {
-    video.pause();
-    await waitForEvent("pause");
-  }
-});
-
-// Bug 1505547: Make sure the fullscreen button works if the video element is in shadow tree.
-add_task(async function ensure_fullscreen_button() {
-  video.removeAttribute("mozNoDynamicControls");
-  let host = document.getElementById("host");
-  let root = host.attachShadow({ mode: "open" });
-  root.appendChild(video);
-
-  video.mozRequestFullScreen();
-  await waitForEvent("mozfullscreenchange");
-
-  const buttonCenterX = screenWidth - Math.round(fullscreenButtonWidth / 2) - controlBarMargin;
-  const buttonCenterY = screenHeight - Math.round(fullscreenButtonHeight / 2);
-
-  // Wiggle the mouse a bit
-  synthesizeMouse(video, buttonCenterX, buttonCenterY, { type: "mousemove" });
-
-  synthesizeMouse(video, buttonCenterX, buttonCenterY, {});
-  await waitForEvent("mozfullscreenchange");
-
-  // Restore
-  video.setAttribute("mozNoDynamicControls", "");
-  document.getElementById("content").appendChild(video);
-});
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_audio.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls with Audio file test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video id="video" controls preload="metadata"></video>
-</div>
-
-<pre id="test">
-<script class="testbody" type="application/javascript">
-
-  const InspectorUtils = SpecialPowers.InspectorUtils;
-
-  function findElementByAttribute(element, aName, aValue) {
-    if (!("getAttribute" in element)) {
-      return false;
-    }
-    if (element.getAttribute(aName) === aValue) {
-      return element;
-    }
-    let children =
-      InspectorUtils.getChildrenForNode(element, true);
-    for (let child of children) {
-      var result = findElementByAttribute(child, aName, aValue);
-      if (result) {
-        return result;
-      }
-    }
-    return false;
-  }
-
-  function loadedmetadata(event) {
-    SimpleTest.executeSoon(function() {
-      var controlBar = findElementByAttribute(video, "class", "controlBar");
-      is(controlBar.getAttribute("fullscreen-unavailable"), "true", "Fullscreen button is hidden");
-      SimpleTest.finish();
-    });
-  }
-
-  var video = document.getElementById("video");
-
-  SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startTest);
-  function startTest() {
-    // Kick off test once audio has loaded.
-    video.addEventListener("loadedmetadata", loadedmetadata);
-    video.src = "audio.ogg";
-  }
-
-  SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_audio_direction.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls directionality test</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
-  <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-</div>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-var tests = [
-  {op: "==", test: "videocontrols_direction-2a.html", ref: "videocontrols_direction-2-ref.html"},
-  {op: "==", test: "videocontrols_direction-2b.html", ref: "videocontrols_direction-2-ref.html"},
-  {op: "==", test: "videocontrols_direction-2c.html", ref: "videocontrols_direction-2-ref.html"},
-  {op: "==", test: "videocontrols_direction-2d.html", ref: "videocontrols_direction-2-ref.html"},
-  {op: "==", test: "videocontrols_direction-2e.html", ref: "videocontrols_direction-2-ref.html"},
-];
-
-</script>
-<script type="text/javascript" src="videocontrols_direction_test.js"></script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_error.html
+++ /dev/null
@@ -1,58 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test - Error</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video id="video" controls preload="auto"></video>
-</div>
-
-<pre id="test">
-<script clas="testbody" type="application/javascript">
-  const video = document.getElementById("video");
-  const statusOverlay = getAnonElementWithinVideoByAttribute(video, "anonid", "statusOverlay");
-  const statusIcon = getAnonElementWithinVideoByAttribute(video, "anonid", "statusIcon");
-
-  add_task(async function setup() {
-    await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
-  });
-
-  add_task(async function check_normal_status() {
-    await new Promise(resolve => {
-      video.src = "seek_with_sound.ogg";
-      video.addEventListener("loadedmetadata", () => SimpleTest.executeSoon(resolve));
-    });
-
-    // Wait for the fade out transition to complete in case the throbber
-    // shows up on slower platforms.
-    await SimpleTest.promiseWaitForCondition(() => statusOverlay.hidden,
-      "statusOverlay should not present without error");
-
-    ok(!statusOverlay.hasAttribute("error"), "statusOverlay should not in error state");
-    isnot(statusIcon.getAttribute("type"), "error", "should not show error icon");
-  });
-
-  add_task(async function invalid_source() {
-    const errorType = "errorNoSource";
-
-    await new Promise(resolve => {
-      video.src = "invalid_source.ogg";
-      video.addEventListener("error", () => SimpleTest.executeSoon(resolve));
-    });
-
-    ok(!statusOverlay.hidden, `statusOverlay should show when ${errorType}`);
-    is(statusOverlay.getAttribute("error"), errorType, `statusOverlay should have correct error state: ${errorType}`);
-    is(statusIcon.getAttribute("type"), "error", `should show error icon when ${errorType}`);
-  });
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_iframe_fullscreen.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test - iframe</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-<iframe id="ifr1"></iframe>
-<iframe id="ifr2" allowfullscreen></iframe>
-</div>
-
-<pre id="test">
-<script clas="testbody" type="application/javascript">
-  SimpleTest.waitForExplicitFinish();
-
-  const InspectorUtils = SpecialPowers.InspectorUtils;
-
-  const iframe1 = document.getElementById("ifr1");
-  const iframe2 = document.getElementById("ifr2");
-  const testCases = [];
-
-  function checkIframeFullscreenAvailable(ifr) {
-    const available = ifr.hasAttribute("allowfullscreen");
-    let video;
-
-    return () => new Promise(resolve => {
-      ifr.srcdoc = `<video id="video" controls preload="auto"></video>`;
-      ifr.addEventListener("load", resolve);
-    }).then(() => new Promise(resolve => {
-      video = ifr.contentDocument.getElementById("video");
-      video.src = "seek_with_sound.ogg";
-      video.addEventListener("loadedmetadata", resolve);
-    })).then(() => new Promise(resolve => {
-      const children = InspectorUtils.getChildrenForNode(video, true);
-      const videoControl = children[1];
-      const doc = SpecialPowers.wrap(video.ownerDocument);
-      const controlBar = doc.getAnonymousElementByAttribute(
-        videoControl, "class", "controlBar");
-
-      is(controlBar.getAttribute("fullscreen-unavailable") == "true", !available, "The controlbar should have an attribute marking whether fullscreen is available that corresponds to if the iframe has the allowfullscreen attribute.");
-      resolve();
-    }));
-  }
-
-  function start() {
-    testCases.reduce((promise, task) => promise.then(task), Promise.resolve());
-  }
-
-  function load() {
-    SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, start);
-  }
-
-  testCases.push(checkIframeFullscreenAvailable(iframe1));
-  testCases.push(checkIframeFullscreenAvailable(iframe2));
-  testCases.push(SimpleTest.finish);
-
-  window.addEventListener("load", load);
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_jsdisabled.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-function runTest(event) {
-  info(true, "----- test #" + testnum + " -----");
-
-  switch (testnum) {
-    case 1:
-      is(event.type, "timeupdate", "checking event type");
-      is(video.paused, false, "checking video play state");
-      video.removeEventListener("timeupdate", runTest);
-
-      // Click to toggle play/pause (now pausing)
-      synthesizeMouseAtCenter(video, {}, win);
-      break;
-
-    case 2:
-      is(event.type, "pause", "checking event type");
-      is(video.paused, true, "checking video play state");
-      win.close();
-
-      SimpleTest.finish();
-      break;
-
-    default:
-      ok(false, "unexpected test #" + testnum + " w/ event " + event.type);
-      throw "unexpected test #" + testnum + " w/ event " + event.type;
-  }
-
-  testnum++;
-}
-
-SpecialPowers.pushPrefEnv({"set": [["javascript.enabled", false]]}, startTest);
-
-var testnum = 1;
-
-var video;
-function loadevent(event) {
-  is(win.testExpando, undefined, "expando shouldn't exist because js is disabled");
-  video = win.document.querySelector("video");
-  // Other events expected by the test.
-  video.addEventListener("timeupdate", runTest);
-  video.addEventListener("pause", runTest);
-}
-
-var win;
-function startTest() {
-  const TEST_FILE = location.href.replace("test_videocontrols_jsdisabled.html",
-                                          "file_videocontrols_jsdisabled.html");
-  win = window.open(TEST_FILE);
-  win.addEventListener("load", loadevent);
-}
-
-SimpleTest.waitForExplicitFinish();
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_keyhandler.html
+++ /dev/null
@@ -1,69 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test - KeyHandler</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video id="video" controls preload="auto"></video>
-</div>
-
-<pre id="test">
-<script class="testbody" type="application/javascript">
-  SimpleTest.waitForExplicitFinish();
-  const video = document.getElementById("video");
-
-  const playButton = getAnonElementWithinVideoByAttribute(video, "anonid", "playButton");
-  const scrubber = getAnonElementWithinVideoByAttribute(video, "anonid", "scrubber");
-  const volumeStack = getAnonElementWithinVideoByAttribute(video, "anonid", "volumeStack");
-
-  // Setup video
-  tests.push(done => {
-    SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, done);
-  }, done => {
-    video.src = "seek_with_sound.ogg";
-    video.addEventListener("loadedmetadata", done);
-  });
-
-  // Bug 1350191, video should not seek while changing volume by
-  // pressing up/down arrow key.
-  tests.push(done => {
-    video.addEventListener("play", done, { once: true });
-    synthesizeMouseAtCenter(playButton, {});
-  }, done => {
-    video.addEventListener("seeked", done, { once: true });
-    synthesizeMouseAtCenter(scrubber, {});
-  }, done => {
-    let counter = 0;
-    let keys = ["KEY_ArrowDown", "KEY_ArrowDown", "KEY_ArrowUp", "KEY_ArrowDown", "KEY_ArrowUp", "KEY_ArrowUp"];
-
-    video.addEventListener("seeked", () => ok(false, "should not trigger seeked event"));
-    video.addEventListener("volumechange", () => {
-      if (++counter === keys.length) {
-        ok(true, "change volume by up/down arrow key without trigger 'seeked' event");
-        done();
-      }
-
-      if (counter > keys.length) {
-        ok(false, "trigger too much volumechange events");
-      }
-    });
-
-    for (let key of keys) {
-      synthesizeKey(key);
-    }
-  });
-
-  tests.push(SimpleTest.finish);
-
-  window.addEventListener("load", executeTests);
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_onclickplay.html
+++ /dev/null
@@ -1,74 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test</title>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video id="video" controls mozNoDynamicControls preload="auto"></video>
-</div>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-var video = document.getElementById("video");
-
-function startMediaLoad() {
-  // Kick off test once video has loaded, in its canplaythrough event handler.
-  video.src = "seek_with_sound.ogg";
-  video.addEventListener("canplaythrough", runTest);
-}
-
-function loadevent(event) {
-  SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startMediaLoad);
-}
-
-window.addEventListener("load", loadevent);
-
-function runTest() {
-  video.addEventListener("click", function() {
-    this.play();
-  });
-  ok(video.paused, "video should be paused initially");
-
-  new Promise(resolve => {
-    let timeupdates = 0;
-    video.addEventListener("timeupdate", function timeupdate() {
-      ok(!video.paused, "video should not get paused after clicking in middle");
-
-      if (++timeupdates == 3) {
-        video.removeEventListener("timeupdate", timeupdate);
-        resolve();
-      }
-    });
-
-    synthesizeMouseAtCenter(video, {}, window);
-  }).then(function() {
-    new Promise(resolve => {
-      video.addEventListener("pause", function onpause() {
-        setTimeout(() => {
-          ok(video.paused, "video should still be paused 200ms after pause request");
-          // When the video reaches the end of playback it is automatically paused.
-          // Check during the pause event that the video has not reachd the end
-          // of playback.
-          ok(!video.ended, "video should not have paused due to playback ending");
-          resolve();
-        }, 200);
-      });
-
-      synthesizeMouse(video, 10, video.clientHeight - 10, {}, window);
-    }).then(SimpleTest.finish);
-  });
-}
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_orientation.html
+++ /dev/null
@@ -1,63 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video id="video" controls mozNoDynamicControls preload="metadata"></video>
-</div>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-SimpleTest.waitForExplicitFinish();
-var video = document.getElementById("video");
-
-let onLoaded = event => {
-  SpecialPowers.pushPrefEnv(
-    {"set": [["full-screen-api.allow-trusted-requests-only", false],
-             ["media.videocontrols.lock-video-orientation", true]]},
-    startMediaLoad);
-};
-window.addEventListener("load", onLoaded);
-
-let startMediaLoad = () => {
-  // Kick off test once video has loaded, in its canplaythrough event handler.
-  video.src = "video.ogg";
-  video.addEventListener("canplaythrough", runTest);
-};
-
-function runTest() {
-  is(document.mozFullScreenElement, null, "should not be in fullscreen initially");
-  isnot(window.screen.orientation.type, "landscape-primary", "should not be in landscape");
-  isnot(window.screen.orientation.type, "landscape-secondary", "should not be in landscape");
-
-  let originalOnChange = window.screen.orientation.onchange;
-
-  window.screen.orientation.onchange = () => {
-    is(document.mozFullScreenElement, video, "should be in fullscreen");
-    ok(window.screen.orientation.type === "landscape-primary" ||
-       window.screen.orientation.type === "landscape-secondary", "should be in landscape");
-
-    window.screen.orientation.onchange = () => {
-      window.screen.orientation.onchange = originalOnChange;
-      isnot(window.screen.orientation.type, "landscape-primary", "should not be in landscape");
-      isnot(window.screen.orientation.type, "landscape-secondary", "should not be in landscape");
-      SimpleTest.finish();
-    };
-    document.mozCancelFullScreen();
-  };
-
-  video.mozRequestFullScreen();
-}
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_size.html
+++ /dev/null
@@ -1,179 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test - Size</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video controls preload="auto" width="480" height="320"></video>
-  <video controls preload="auto" width="320" height="320"></video>
-  <video controls preload="auto" width="280" height="320"></video>
-  <video controls preload="auto" width="240" height="320"></video>
-  <video controls preload="auto" width="180" height="320"></video>
-  <video controls preload="auto" width="120" height="320"></video>
-  <video controls preload="auto" width="60" height="320"></video>
-  <video controls preload="auto" width="48" height="320"></video>
-  <video controls preload="auto" width="20" height="320"></video>
-
-  <video controls preload="auto" width="480" height="240"></video>
-  <video controls preload="auto" width="480" height="120"></video>
-  <video controls preload="auto" width="480" height="39"></video>
-</div>
-
-<pre id="test">
-<script clas="testbody" type="application/javascript">
-  SimpleTest.waitForExplicitFinish();
-
-  const videoElems = [...document.getElementsByTagName("video")];
-  const testCases = [];
-
-  const isTouchControl = navigator.appVersion.includes("Android");
-
-  const buttonWidth = isTouchControl ? 40 : 30;
-  const minSrubberWidth = isTouchControl ? 64 : 48;
-  const minControlBarHeight = isTouchControl ? 52 : 40;
-  const minControlBarWidth = isTouchControl ? 58 : 48;
-  const minClickToPlaySize = isTouchControl ? 64 : 48;
-
-  function getElementName(elem) {
-    return elem.getAttribute("anonid") || elem.getAttribute("class");
-  }
-
-  function testButton(btn) {
-    if (btn.hidden) return;
-
-    const rect = btn.getBoundingClientRect();
-    const name = getElementName(btn);
-
-    is(rect.width, buttonWidth, `${name} should have correct width`);
-    is(rect.height, minControlBarHeight, `${name} should have correct height`);
-  }
-
-  function testScrubber(scrubber) {
-    if (scrubber.hidden) return;
-
-    const rect = scrubber.getBoundingClientRect();
-    const name = getElementName(scrubber);
-
-    ok(rect.width >= minSrubberWidth, `${name} should longer than ${minSrubberWidth}`);
-  }
-
-  function testUI(video) {
-    video.style.display = "block";
-    video.getBoundingClientRect();
-    video.style.display = "";
-
-    const videoRect = video.getBoundingClientRect();
-
-    const videoHeight = video.clientHeight;
-    const videoWidth = video.clientWidth;
-
-    const videoSizeMsg = `size:${videoRect.width}x${videoRect.height} -`;
-    const controlBar = getAnonElementWithinVideoByAttribute(video, "anonid", "controlBar");
-    const playBtn = getAnonElementWithinVideoByAttribute(video, "anonid", "playButton");
-    const scrubber = getAnonElementWithinVideoByAttribute(video, "anonid", "scrubberStack");
-    const positionDurationBox = getAnonElementWithinVideoByAttribute(video, "anonid", "positionDurationBox");
-    const durationLabel = positionDurationBox.getElementsByTagName("span")[0];
-    const muteBtn = getAnonElementWithinVideoByAttribute(video, "anonid", "muteButton");
-    const volumeStack = getAnonElementWithinVideoByAttribute(video, "anonid", "volumeStack");
-    const fullscreenBtn = getAnonElementWithinVideoByAttribute(video, "anonid", "fullscreenButton");
-    const clickToPlay = getAnonElementWithinVideoByAttribute(video, "anonid", "clickToPlay");
-
-
-    // Controls should show/hide according to the priority
-    const prioritizedControls = [
-      playBtn,
-      muteBtn,
-      fullscreenBtn,
-      positionDurationBox,
-      scrubber,
-      durationLabel,
-      volumeStack,
-    ];
-
-    let stopAppend = false;
-    prioritizedControls.forEach(control => {
-      is(control.hidden, stopAppend = stopAppend || control.hidden,
-        `${videoSizeMsg} ${getElementName(control)} should ${stopAppend ? "hide" : "show"}`);
-    });
-
-
-    // All controls should fit in control bar container
-    const controls = [
-      playBtn,
-      scrubber,
-      positionDurationBox,
-      muteBtn,
-      volumeStack,
-      fullscreenBtn,
-    ];
-
-    let widthSum = 0;
-    controls.forEach(control => {
-      widthSum += control.clientWidth;
-    });
-    ok(videoWidth >= widthSum,
-      `${videoSizeMsg} controlBar fit in video's width`);
-
-
-    // Control bar should show/hide according to video's dimensions
-    const shouldHideControlBar = videoHeight <= minControlBarHeight ||
-      videoWidth < minControlBarWidth;
-    is(controlBar.hidden, shouldHideControlBar, `${videoSizeMsg} controlBar show/hide`);
-
-    if (!shouldHideControlBar) {
-      is(controlBar.clientWidth, videoWidth, `control bar width should equal to video width`);
-
-      // Check all controls' dimensions
-      testButton(playBtn);
-      testButton(muteBtn);
-      testButton(fullscreenBtn);
-      testScrubber(scrubber);
-      testScrubber(volumeStack);
-    }
-
-
-    // ClickToPlay button should show if min size can fit in
-    const shouldHideClickToPlay = videoWidth <= minClickToPlaySize ||
-      (videoHeight - minClickToPlaySize) / 2 <= minControlBarHeight;
-    is(clickToPlay.hidden, shouldHideClickToPlay, `${videoSizeMsg} clickToPlay show/hide`);
-  }
-
-
-  testCases.push(() => Promise.all(videoElems.map(video => new Promise(resolve => {
-    video.addEventListener("loadedmetadata", resolve);
-    video.src = "seek_with_sound.ogg";
-  }))));
-
-  videoElems.forEach(video => {
-    testCases.push(() => new Promise(resolve => {
-      SimpleTest.executeSoon(() => {
-        testUI(video);
-        resolve();
-      });
-    }));
-  });
-
-  function executeTasks(tasks) {
-    return tasks.reduce((promise, task) => promise.then(task), Promise.resolve());
-  }
-
-  function start() {
-    executeTasks(testCases).then(SimpleTest.finish);
-  }
-
-  function loadevent() {
-    SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, start);
-  }
-
-  window.addEventListener("load", loadevent);
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_standalone.html
+++ /dev/null
@@ -1,87 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-  SimpleTest.expectAssertions(0, 1);
-
-const videoWidth = 320;
-const videoHeight = 240;
-
-function getMediaElement(aWindow) {
-  return aWindow.document.getElementsByTagName("video")[0];
-}
-
-var popup = window.open("seek_with_sound.ogg");
-popup.addEventListener("load", function() {
-  var video = getMediaElement(popup);
-  if (!video.paused)
-    runTestVideo(video);
-  else {
-    video.addEventListener("play", function() {
-      runTestVideo(video);
-    }, {once: true});
-  }
-}, {once: true});
-
-function runTestVideo(aVideo) {
-  var condition = function() {
-    var boundingRect = aVideo.getBoundingClientRect();
-    return boundingRect.width == videoWidth &&
-           boundingRect.height == videoHeight;
-  };
-  waitForCondition(condition, function() {
-    var boundingRect = aVideo.getBoundingClientRect();
-    is(boundingRect.width, videoWidth, "Width of the video should match expectation");
-    is(boundingRect.height, videoHeight, "Height of video should match expectation");
-    popup.close();
-    runTestAudioPre();
-  }, "The media element should eventually be resized to match the intrinsic size of the video.");
-}
-
-function runTestAudioPre() {
-  popup = window.open("audio.ogg");
-  popup.addEventListener("load", function() {
-    var audio = getMediaElement(popup);
-    if (!audio.paused)
-      runTestAudio(audio);
-    else {
-      audio.addEventListener("play", function() {
-        runTestAudio(audio);
-      }, {once: true});
-    }
-  }, {once: true});
-}
-
-function runTestAudio(aAudio) {
-  info("User agent (help diagnose bug #943556): " + navigator.userAgent);
-  var isAndroid = navigator.userAgent.includes("Android");
-  var expectedHeight = isAndroid ? 103 : 40;
-  var condition = function() {
-    var boundingRect = aAudio.getBoundingClientRect();
-    return boundingRect.height == expectedHeight;
-  };
-  waitForCondition(condition, function() {
-    var boundingRect = aAudio.getBoundingClientRect();
-    is(boundingRect.height, expectedHeight,
-       "Height of audio element should be " + expectedHeight + ", which is equal to the controls bar.");
-    popup.close();
-    SimpleTest.finish();
-  }, "The media element should eventually be resized to match the height of the audio controls.");
-}
-
-SimpleTest.waitForExplicitFinish();
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_video_direction.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls directionality test</title>
-  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>  
-  <script type="text/javascript" src="/tests/SimpleTest/WindowSnapshot.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-</div>
-
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-var tests = [
-  {op: "==", test: "videocontrols_direction-1a.html", ref: "videocontrols_direction-1-ref.html"},
-  {op: "==", test: "videocontrols_direction-1b.html", ref: "videocontrols_direction-1-ref.html"},
-  {op: "==", test: "videocontrols_direction-1c.html", ref: "videocontrols_direction-1-ref.html"},
-  {op: "==", test: "videocontrols_direction-1d.html", ref: "videocontrols_direction-1-ref.html"},
-  {op: "==", test: "videocontrols_direction-1e.html", ref: "videocontrols_direction-1-ref.html"},
-];
-
-</script>
-<script type="text/javascript" src="videocontrols_direction_test.js"></script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_video_noaudio.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video id="video" controls preload="auto"></video>
-</div>
-
-<pre id="test">
-<script clas="testbody" type="application/javascript">
-  const video = document.getElementById("video");
-  const muteButton = getAnonElementWithinVideoByAttribute(video, "anonid", "muteButton");
-  const volumeStack = getAnonElementWithinVideoByAttribute(video, "anonid", "volumeStack");
-
-  add_task(async function setup() {
-    await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
-    await new Promise(resolve => {
-      video.src = "video.ogg";
-      video.addEventListener("loadedmetadata", () => SimpleTest.executeSoon(resolve));
-    });
-  });
-
-  add_task(async function mute_button_icon() {
-    is(muteButton.getAttribute("noAudio"), "true");
-    is(muteButton.getAttribute("disabled"), "true");
-
-    if (volumeStack) {
-      ok(volumeStack.hidden);
-    }
-  });
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/tests/widgets/xbl/test_videocontrols_vtt.html
+++ /dev/null
@@ -1,109 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Video controls test - VTT</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/AddTask.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
-  <script type="text/javascript" src="head.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<p id="display"></p>
-
-<div id="content">
-  <video id="video" controls preload="auto"></video>
-</div>
-
-<pre id="test">
-<script clas="testbody" type="application/javascript">
-  SimpleTest.waitForExplicitFinish();
-
-  const video = document.getElementById("video");
-  const ccBtn = getAnonElementWithinVideoByAttribute(video, "anonid", "closedCaptionButton");
-  const ttList = getAnonElementWithinVideoByAttribute(video, "anonid", "textTrackList");
-
-  add_task(async function wait_for_media_ready() {
-    await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
-    await new Promise(resolve => {
-      video.src = "seek_with_sound.ogg";
-      video.addEventListener("loadedmetadata", resolve);
-    });
-  });
-
-  add_task(async function check_inital_state() {
-    ok(ccBtn.hidden, "CC button should hide");
-  });
-
-  add_task(async function check_unsupported_type_added() {
-    video.addTextTrack("descriptions", "English", "en");
-    video.addTextTrack("chapters", "English", "en");
-    video.addTextTrack("metadata", "English", "en");
-
-    await new Promise(SimpleTest.executeSoon);
-    ok(ccBtn.hidden, "CC button should hide if no supported tracks provided");
-  });
-
-  add_task(async function check_cc_button_present() {
-    const sub = video.addTextTrack("subtitles", "English", "en");
-    sub.mode = "disabled";
-
-    await new Promise(SimpleTest.executeSoon);
-    ok(!ccBtn.hidden, "CC button should show");
-    is(ccBtn.hasAttribute("enabled"), false, "CC button should be disabled");
-  });
-
-  add_task(async function check_cc_button_be_enabled() {
-    const subtitle = video.addTextTrack("subtitles", "English", "en");
-    subtitle.mode = "showing";
-
-    await new Promise(SimpleTest.executeSoon);
-    ok(ccBtn.hasAttribute("enabled"), "CC button should be enabled");
-    subtitle.mode = "disabled";
-  });
-
-  add_task(async function check_cpations_type() {
-    const caption = video.addTextTrack("captions", "English", "en");
-    caption.mode = "showing";
-
-    await new Promise(SimpleTest.executeSoon);
-    ok(ccBtn.hasAttribute("enabled"), "CC button should be enabled");
-  });
-
-  add_task(async function check_track_ui_state() {
-    synthesizeMouseAtCenter(ccBtn, {});
-
-    await new Promise(SimpleTest.executeSoon);
-    ok(!ttList.hidden, "Texttrack menu should show up");
-    ok(ttList.lastChild.hasAttribute("on"), "The last added item should be highlighted");
-  });
-
-  add_task(async function check_select_texttrack() {
-    const tt = ttList.children[1];
-
-    ok(!tt.hasAttribute("on"), "Item should be off before click");
-    synthesizeMouseAtCenter(tt, {});
-
-    await new Promise(SimpleTest.executeSoon);
-    ok(tt.hasAttribute("on"), "Selected item should be enabled");
-    ok(ttList.hidden, "Should hide texttrack menu once clicked on an item");
-  });
-
-  add_task(async function check_change_texttrack_mode() {
-    const tts = [...video.textTracks];
-
-    tts.forEach(tt => tt.mode = "hidden");
-    await new Promise(SimpleTest.executeSoon);
-    ok(!ccBtn.hasAttribute("enabled"), "CC button should be disabled");
-
-    // enable the last text track.
-    tts[tts.length - 1].mode = "showing";
-    await new Promise(SimpleTest.executeSoon);
-    ok(ccBtn.hasAttribute("enabled"), "CC button should be enabled");
-    ok(ttList.lastChild.hasAttribute("on"), "The last item should be highlighted");
-  });
-
-</script>
-</pre>
-</body>
-</html>
deleted file mode 100644
--- a/toolkit/content/widgets/videocontrols.xml
+++ /dev/null
@@ -1,2305 +0,0 @@
-<?xml version="1.0"?>
-<!-- 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/. -->
-
-<!DOCTYPE bindings [
-<!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
-%videocontrolsDTD;
-]>
-
-<bindings id="videoControlBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl"
-          xmlns:svg="http://www.w3.org/2000/svg"
-          xmlns:html="http://www.w3.org/1999/xhtml">
-
-<binding id="videoControls">
-  <resources>
-    <stylesheet src="chrome://global/skin/media/videocontrols.css"/>
-  </resources>
-
-  <xbl:content xmlns="http://www.w3.org/1999/xhtml" class="mediaControlsFrame">
-    <div anonid="controlsContainer" class="controlsContainer" role="none">
-      <div anonid="statusOverlay" class="statusOverlay stackItem" hidden="true">
-        <div anonid="statusIcon" class="statusIcon"></div>
-        <span class="errorLabel" anonid="errorAborted">&error.aborted;</span>
-        <span class="errorLabel" anonid="errorNetwork">&error.network;</span>
-        <span class="errorLabel" anonid="errorDecode">&error.decode;</span>
-        <span class="errorLabel" anonid="errorSrcNotSupported">&error.srcNotSupported;</span>
-        <span class="errorLabel" anonid="errorNoSource">&error.noSource2;</span>
-        <span class="errorLabel" anonid="errorGeneric">&error.generic;</span>
-      </div>
-
-      <div anonid="controlsOverlay" class="controlsOverlay stackItem">
-        <div class="controlsSpacerStack">
-          <div anonid="controlsSpacer" class="controlsSpacer stackItem" role="none"></div>
-          <div anonid="clickToPlay" class="clickToPlay" hidden="true"></div>
-        </div>
-        <div anonid="controlBar" class="controlBar" hidden="true">
-          <button anonid="playButton"
-                  class="button playButton"
-                  playlabel="&playButton.playLabel;"
-                  pauselabel="&playButton.pauseLabel;"
-                  tabindex="-1"/>
-          <div anonid="scrubberStack" class="scrubberStack progressContainer" role="none">
-            <div class="progressBackgroundBar stackItem" role="none">
-              <div class="progressStack" role="none">
-                <progress anonid="bufferBar" class="bufferBar" value="0" max="100" tabindex="-1"></progress>
-                <progress anonid="progressBar" class="progressBar" value="0" max="100" tabindex="-1"></progress>
-              </div>
-            </div>
-            <input type="range" anonid="scrubber" class="scrubber" tabindex="-1"/>
-          </div>
-          <span anonid="positionLabel" class="positionLabel" role="presentation"></span>
-          <span anonid="durationLabel" class="durationLabel" role="presentation"></span>
-          <span anonid="positionDurationBox" class="positionDurationBox" aria-hidden="true">
-            &positionAndDuration.nameFormat;
-          </span>
-          <div anonid="controlBarSpacer" class="controlBarSpacer" hidden="true" role="none"></div>
-          <button anonid="muteButton"
-                  class="button muteButton"
-                  mutelabel="&muteButton.muteLabel;"
-                  unmutelabel="&muteButton.unmuteLabel;"
-                  tabindex="-1"/>
-          <div anonid="volumeStack" class="volumeStack progressContainer" role="none">
-            <input type="range" anonid="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1"
-                   aria-label="&volumeScrubber.label;"/>
-          </div>
-          <button anonid="castingButton" class="button castingButton"
-                  aria-label="&castingButton.castingLabel;"/>
-          <button anonid="closedCaptionButton" class="button closedCaptionButton"/>
-          <button anonid="fullscreenButton"
-                  class="button fullscreenButton"
-                  enterfullscreenlabel="&fullscreenButton.enterfullscreenlabel;"
-                  exitfullscreenlabel="&fullscreenButton.exitfullscreenlabel;"/>
-        </div>
-        <div anonid="textTrackList" class="textTrackList" hidden="true" offlabel="&closedCaption.off;"></div>
-      </div>
-    </div>
-  </xbl:content>
-
-  <implementation>
-
-  <constructor>
-    <![CDATA[
-    this.randomID = 0;
-
-    this.Utils = {
-      debug: false,
-      video: null,
-      videocontrols: null,
-      controlBar: null,
-      playButton: null,
-      muteButton: null,
-      volumeControl: null,
-      durationLabel: null,
-      positionLabel: null,
-      scrubber: null,
-      progressBar: null,
-      bufferBar: null,
-      statusOverlay: null,
-      controlsSpacer: null,
-      clickToPlay: null,
-      controlsOverlay: null,
-      fullscreenButton: null,
-      layoutControls: null,
-
-      textTracksCount: 0,
-      randomID: 0,
-      videoEvents: ["play", "pause", "ended", "volumechange", "loadeddata",
-                    "loadstart", "timeupdate", "progress",
-                    "playing", "waiting", "canplay", "canplaythrough",
-                    "seeking", "seeked", "emptied", "loadedmetadata",
-                    "error", "suspend", "stalled",
-                    "mozvideoonlyseekbegin", "mozvideoonlyseekcompleted"],
-
-      showHours: false,
-      firstFrameShown: false,
-      timeUpdateCount: 0,
-      maxCurrentTimeSeen: 0,
-      isPausedByDragging: false,
-      _isAudioOnly: false,
-
-      get isAudioOnly() { return this._isAudioOnly; },
-      set isAudioOnly(val) {
-        this._isAudioOnly = val;
-        this.setFullscreenButtonState();
-
-        if (!this.isTopLevelSyntheticDocument) {
-          return;
-        }
-        if (this._isAudioOnly) {
-          this.video.style.height = this.controlBarMinHeight + "px";
-          this.video.style.width = "66%";
-        } else {
-          this.video.style.removeProperty("height");
-          this.video.style.removeProperty("width");
-        }
-      },
-
-      suppressError: false,
-
-      setupStatusFader(immediate) {
-        // Since the play button will be showing, we don't want to
-        // show the throbber behind it. The throbber here will
-        // only show if needed after the play button has been pressed.
-        if (!this.clickToPlay.hidden) {
-          this.startFadeOut(this.statusOverlay, true);
-          return;
-        }
-
-        var show = false;
-        if (this.video.seeking ||
-            (this.video.error && !this.suppressError) ||
-            this.video.networkState == this.video.NETWORK_NO_SOURCE ||
-            (this.video.networkState == this.video.NETWORK_LOADING &&
-              (this.video.paused || this.video.ended
-                ? this.video.readyState < this.video.HAVE_CURRENT_DATA
-                : this.video.readyState < this.video.HAVE_FUTURE_DATA)) ||
-            (this.timeUpdateCount <= 1 && !this.video.ended &&
-             this.video.readyState < this.video.HAVE_FUTURE_DATA &&
-             this.video.networkState == this.video.NETWORK_LOADING)) {
-          show = true;
-        }
-
-        // Explicitly hide the status fader if this
-        // is audio only until bug 619421 is fixed.
-        if (this.isAudioOnly) {
-          show = false;
-        }
-
-        if (this._showThrobberTimer) {
-          show = true;
-        }
-
-        this.log("Status overlay: seeking=" + this.video.seeking +
-                 " error=" + this.video.error + " readyState=" + this.video.readyState +
-                 " paused=" + this.video.paused + " ended=" + this.video.ended +
-                 " networkState=" + this.video.networkState +
-                 " timeUpdateCount=" + this.timeUpdateCount +
-                 " _showThrobberTimer=" + this._showThrobberTimer +
-                 " --> " + (show ? "SHOW" : "HIDE"));
-        this.startFade(this.statusOverlay, show, immediate);
-      },
-
-      /*
-      * Set the initial state of the controls. The binding is normally created along
-      * with video element, but could be attached at any point (eg, if the video is
-      * removed from the document and then reinserted). Thus, some one-time events may
-      * have already fired, and so we'll need to explicitly check the initial state.
-      */
-      setupInitialState() {
-        this.randomID = Math.random();
-        this.videocontrols.randomID = this.randomID;
-
-        this.setPlayButtonState(this.video.paused);
-
-        this.setFullscreenButtonState();
-
-        var duration = Math.round(this.video.duration * 1000); // in ms
-        var currentTime = Math.round(this.video.currentTime * 1000); // in ms
-        this.log("Initial playback position is at " + currentTime + " of " + duration);
-        // It would be nice to retain maxCurrentTimeSeen, but it would be difficult
-        // to determine if the media source changed while we were detached.
-        this.initPositionDurationBox();
-        this.maxCurrentTimeSeen = currentTime;
-        this.showPosition(currentTime, duration);
-
-        // If we have metadata, check if this is a <video> without
-        // video data, or a video with no audio track.
-        if (this.video.readyState >= this.video.HAVE_METADATA) {
-          if (this.video instanceof HTMLVideoElement &&
-              (this.video.videoWidth == 0 || this.video.videoHeight == 0)) {
-            this.isAudioOnly = true;
-          }
-
-          // We have to check again if the media has audio here,
-          // because of bug 718107: switching to fullscreen may
-          // cause the bindings to detach and reattach, hence
-          // unsetting the attribute.
-          if (!this.isAudioOnly && !this.video.mozHasAudio) {
-            this.muteButton.setAttribute("noAudio", "true");
-            this.muteButton.setAttribute("disabled", "true");
-          }
-        }
-
-        // We should lock the orientation if we are already in
-        // fullscreen and were reattached because of bug 718107.
-        this.updateOrientationState(this.isVideoInFullScreen);
-
-        if (this.isAudioOnly) {
-          this.clickToPlay.hidden = true;
-        }
-
-        // If the first frame hasn't loaded, kick off a throbber fade-in.
-        if (this.video.readyState >= this.video.HAVE_CURRENT_DATA) {
-          this.firstFrameShown = true;
-        }
-
-        // We can't determine the exact buffering status, but do know if it's
-        // fully loaded. (If it's still loading, it will fire a progress event
-        // and we'll figure out the exact state then.)
-        this.bufferBar.max = 100;
-        if (this.video.readyState >= this.video.HAVE_METADATA) {
-          this.showBuffered();
-        } else {
-          this.bufferBar.value = 0;
-        }
-
-        // Set the current status icon.
-        if (this.hasError()) {
-          this.clickToPlay.hidden = true;
-          this.statusIcon.setAttribute("type", "error");
-          this.updateErrorText();
-          this.setupStatusFader(true);
-        }
-
-        let adjustableControls = [
-          ...this.prioritizedControls,
-          this.controlBar,
-          this.clickToPlay,
-        ];
-
-        for (let control of adjustableControls) {
-          if (!control) {
-            break;
-          }
-
-          Object.defineProperties(control, {
-            // We should directly access CSSOM to get pre-defined style instead of
-            // retrieving computed dimensions from layout.
-            minWidth: {
-              get: () => {
-                let controlAnonId = control.getAttribute("anonid");
-                let propertyName = `--${controlAnonId}-width`;
-                if (control.modifier) {
-                  propertyName += "-" + control.modifier;
-                }
-                let preDefinedSize = this.controlBarComputedStyles.getPropertyValue(propertyName);
-
-                return parseInt(preDefinedSize, 10);
-              },
-            },
-            isAdjustableControl: {
-              value: true,
-            },
-            modifier: {
-              value: "",
-              writable: true,
-            },
-            isWanted: {
-              value: true,
-              writable: true,
-            },
-            hidden: {
-              set: (v) => {
-                control._isHiddenExplicitly = v;
-                control._updateHiddenAttribute();
-              },
-              get: () => control.hasAttribute("hidden"),
-            },
-            hiddenByAdjustment: {
-              set: (v) => {
-                control._isHiddenByAdjustment = v;
-                control._updateHiddenAttribute();
-              },
-              get: () => control._isHiddenByAdjustment,
-            },
-            _isHiddenByAdjustment: {
-              value: false,
-              writable: true,
-            },
-            _isHiddenExplicitly: {
-              value: false,
-              writable: true,
-            },
-            _updateHiddenAttribute: {
-              value: () => {
-                if (control._isHiddenExplicitly || control._isHiddenByAdjustment) {
-                  control.setAttribute("hidden", "");
-                } else {
-                  control.removeAttribute("hidden");
-                }
-              },
-            },
-          });
-        }
-        this.adjustControlSize();
-
-        // Can only update the volume controls once we've computed
-        // _volumeControlWidth, since the volume slider implementation
-        // depends on it.
-        this.updateVolumeControls();
-      },
-
-      setupNewLoadState() {
-        // videocontrols.css hides the control bar by default, because if script
-        // is disabled our binding's script is disabled too (bug 449358). Thus,
-        // the controls are broken and we don't want them shown. But if script is
-        // enabled, the code here will run and can explicitly unhide the controls.
-        //
-        // For videos with |autoplay| set, we'll leave the controls initially hidden,
-        // so that they don't get in the way of the playing video. Otherwise we'll
-        // go ahead and reveal the controls now, so they're an obvious user cue.
-        //
-        // (Note: the |controls| attribute is already handled via layout/style/html.css)
-        var shouldShow = !this.dynamicControls ||
-                         (this.video.paused &&
-                         !this.video.autoplay);
-        // Hide the overlay if the video time is non-zero or if an error occurred to workaround bug 718107.
-        let shouldClickToPlayShow = shouldShow && !this.isAudioOnly &&
-                                    this.video.currentTime == 0 && !this.hasError();
-        this.startFade(this.clickToPlay, shouldClickToPlayShow, true);
-        this.startFade(this.controlBar, shouldShow, true);
-      },
-
-      get dynamicControls() {
-        // Don't fade controls for <audio> elements.
-        var enabled = !this.isAudioOnly;
-
-        // Allow tests to explicitly suppress the fading of controls.
-        if (this.video.hasAttribute("mozNoDynamicControls")) {
-          enabled = false;
-        }
-
-        // If the video hits an error, suppress controls if it
-        // hasn't managed to do anything else yet.
-        if (!this.firstFrameShown && this.hasError()) {
-          enabled = false;
-        }
-
-        return enabled;
-      },
-
-      updateVolume() {
-        const volume = this.volumeControl.value;
-        this.setVolume(volume / 100);
-      },
-
-      updateVolumeControls() {
-        var volume = this.video.muted ? 0 : this.video.volume;
-        var volumePercentage = Math.round(volume * 100);
-        this.updateMuteButtonState();
-        this.volumeControl.value = volumePercentage;
-      },
-
-      /*
-       * We suspend a video element's video decoder if the video
-       * element is invisible. However, resuming the video decoder
-       * takes time and we show the throbber UI if it takes more than
-       * 250 ms.
-       *
-       * When an already-suspended video element becomes visible, we
-       * resume its video decoder immediately and queue a video-only seek
-       * task to seek the resumed video decoder to the current position;
-       * meanwhile, we also file a "mozvideoonlyseekbegin" event which
-       * we used to start the timer here.
-       *
-       * Once the queued seek operation is done, we dispatch a
-       * "canplay" event which indicates that the resuming operation
-       * is completed.
-       */
-      SHOW_THROBBER_TIMEOUT_MS: 250,
-      _showThrobberTimer: null,
-      _delayShowThrobberWhileResumingVideoDecoder() {
-        this._showThrobberTimer = setTimeout(() => {
-          this.statusIcon.setAttribute("type", "throbber");
-          // Show the throbber immediately since we have waited for SHOW_THROBBER_TIMEOUT_MS.
-          // We don't want to wait for another animation delay(750ms) and the
-          // animation duration(300ms).
-          this.setupStatusFader(true);
-        }, this.SHOW_THROBBER_TIMEOUT_MS);
-      },
-      _cancelShowThrobberWhileResumingVideoDecoder() {
-        if (this._showThrobberTimer) {
-          clearTimeout(this._showThrobberTimer);
-          this._showThrobberTimer = null;
-        }
-      },
-
-      handleEvent(aEvent) {
-        if (!aEvent.isTrusted) {
-          this.log("Drop untrusted event ----> " + aEvent.type);
-          return;
-        }
-
-        this.log("Got event ----> " + aEvent.type);
-
-        // If the binding is detached (or has been replaced by a
-        // newer instance of the binding), nuke our event-listeners.
-        if (this.videocontrols.randomID != this.randomID) {
-          this.terminate();
-          return;
-        }
-
-        if (this.videoEvents.includes(aEvent.type)) {
-          this.handleVideoEvent(aEvent);
-        } else {
-          this.handleControlEvent(aEvent);
-        }
-      },
-
-      handleVideoEvent(aEvent) {
-        switch (aEvent.type) {
-          case "play":
-            this.setPlayButtonState(false);
-            this.setupStatusFader();
-            if (!this._triggeredByControls && this.dynamicControls && this.videocontrols.isTouchControls) {
-              this.startFadeOut(this.controlBar);
-            }
-            if (!this._triggeredByControls) {
-              this.clickToPlay.hidden = true;
-            }
-            this._triggeredByControls = false;
-            break;
-          case "pause":
-            // Little white lie: if we've internally paused the video
-            // while dragging the scrubber, don't change the button state.
-            if (!this.scrubber.isDragging) {
-              this.setPlayButtonState(true);
-            }
-            this.setupStatusFader();
-            break;
-          case "ended":
-            this.setPlayButtonState(true);
-            // We throttle timechange events, so the thumb might not be
-            // exactly at the end when the video finishes.
-            this.showPosition(Math.round(this.video.currentTime * 1000),
-            Math.round(this.video.duration * 1000));
-            this.startFadeIn(this.controlBar);
-            this.setupStatusFader();
-            break;
-          case "volumechange":
-            this.updateVolumeControls();
-            // Show the controls to highlight the changing volume,
-            // but only if the click-to-play overlay has already
-            // been hidden (we don't hide controls when the overlay is visible).
-            if (this.clickToPlay.hidden && !this.isAudioOnly) {
-              this.startFadeIn(this.controlBar);
-              clearTimeout(this._hideControlsTimeout);
-              this._hideControlsTimeout =
-                setTimeout(() => this._hideControlsFn(), this.HIDE_CONTROLS_TIMEOUT_MS);
-            }
-            break;
-          case "loadedmetadata":
-            // If a <video> doesn't have any video data, treat it as <audio>
-            // and show the controls (they won't fade back out)
-            if (this.video instanceof HTMLVideoElement &&
-                (this.video.videoWidth == 0 || this.video.videoHeight == 0)) {
-              this.isAudioOnly = true;
-              this.clickToPlay.hidden = true;
-              this.startFadeIn(this.controlBar);
-              this.setFullscreenButtonState();
-            }
-            this.showPosition(Math.round(this.video.currentTime * 1000), Math.round(this.video.duration * 1000));
-            if (!this.isAudioOnly && !this.video.mozHasAudio) {
-              this.muteButton.setAttribute("noAudio", "true");
-              this.muteButton.setAttribute("disabled", "true");
-            }
-            this.adjustControlSize();
-            break;
-          case "loadeddata":
-            this.firstFrameShown = true;
-            this.setupStatusFader();
-            break;
-          case "loadstart":
-            this.maxCurrentTimeSeen = 0;
-            this.controlsSpacer.removeAttribute("aria-label");
-            this.statusOverlay.removeAttribute("error");
-            this.statusIcon.setAttribute("type", "throbber");
-            this.isAudioOnly = (this.video instanceof HTMLAudioElement);
-            this.setPlayButtonState(true);
-            this.setupNewLoadState();
-            this.setupStatusFader();
-            break;
-          case "progress":
-            this.statusIcon.removeAttribute("stalled");
-            this.showBuffered();
-            this.setupStatusFader();
-            break;
-          case "stalled":
-            this.statusIcon.setAttribute("stalled", "true");
-            this.statusIcon.setAttribute("type", "throbber");
-            this.setupStatusFader();
-            break;
-          case "suspend":
-            this.setupStatusFader();
-            break;
-          case "timeupdate":
-            var currentTime = Math.round(this.video.currentTime * 1000); // in ms
-            var duration = Math.round(this.video.duration * 1000); // in ms
-
-            // If playing/seeking after the video ended, we won't get a "play"
-            // event, so update the button state here.
-            if (!this.video.paused) {
-              this.setPlayButtonState(false);
-            }
-
-            this.timeUpdateCount++;
-            // Whether we show the statusOverlay sometimes depends
-            // on whether we've seen more than one timeupdate
-            // event (if we haven't, there hasn't been any
-            // "playback activity" and we may wish to show the
-            // statusOverlay while we wait for HAVE_ENOUGH_DATA).
-            // If we've seen more than 2 timeupdate events,
-            // the count is no longer relevant to setupStatusFader.
-            if (this.timeUpdateCount <= 2) {
-              this.setupStatusFader();
-            }
-
-            // If the user is dragging the scrubber ignore the delayed seek
-            // responses (don't yank the thumb away from the user)
-            if (this.scrubber.isDragging) {
-              return;
-            }
-            this.showPosition(currentTime, duration);
-            this.showBuffered();
-            break;
-          case "emptied":
-            this.bufferBar.value = 0;
-            this.showPosition(0, 0);
-            break;
-          case "seeking":
-            this.showBuffered();
-            this.statusIcon.setAttribute("type", "throbber");
-            this.setupStatusFader();
-            break;
-          case "waiting":
-            this.statusIcon.setAttribute("type", "throbber");
-            this.setupStatusFader();
-            break;
-          case "seeked":
-          case "playing":
-          case "canplay":
-          case "canplaythrough":
-            this.setupStatusFader();
-            break;
-          case "error":
-            // We'll show the error status icon when we receive an error event
-            // under either of the following conditions:
-            // 1. The video has its error attribute set; this means we're loading
-            //    from our src attribute, and the load failed, or we we're loading
-            //    from source children and the decode or playback failed after we
-            //    determined our selected resource was playable.
-            // 2. The video's networkState is NETWORK_NO_SOURCE. This means we we're
-            //    loading from child source elements, but we were unable to select
-            //    any of the child elements for playback during resource selection.
-            if (this.hasError()) {
-              this.suppressError = false;
-              this.clickToPlay.hidden = true;
-              this.statusIcon.setAttribute("type", "error");
-              this.updateErrorText();
-              this.setupStatusFader(true);
-              // If video hasn't shown anything yet, disable the controls.
-              if (!this.firstFrameShown && !this.isAudioOnly) {
-                this.startFadeOut(this.controlBar);
-              }
-              this.controlsSpacer.removeAttribute("hideCursor");
-            }
-            break;
-          case "mozvideoonlyseekbegin":
-            this._delayShowThrobberWhileResumingVideoDecoder();
-            break;
-          case "mozvideoonlyseekcompleted":
-            this._cancelShowThrobberWhileResumingVideoDecoder();
-            this.setupStatusFader();
-            break;
-          default:
-            this.log("!!! media event " + aEvent.type + " not handled!");
-        }
-      },
-
-      handleControlEvent(aEvent) {
-        switch (aEvent.type) {
-          case "click":
-            switch (aEvent.currentTarget) {
-              case this.muteButton:
-                this.toggleMute();
-                break;
-              case this.castingButton:
-                this.toggleCasting();
-                break;
-              case this.closedCaptionButton:
-                this.toggleClosedCaption();
-                break;
-              case this.fullscreenButton:
-                this.toggleFullscreen();
-                break;
-              case this.playButton:
-              case this.clickToPlay:
-              case this.controlsSpacer:
-                this.clickToPlayClickHandler(aEvent);
-                break;
-              case this.textTrackList:
-                const index = +aEvent.originalTarget.getAttribute("index");
-                this.changeTextTrack(index);
-                break;
-              case this.videocontrols:
-                // Prevent any click event within media controls from dispatching through to video.
-                aEvent.stopPropagation();
-                break;
-            }
-            break;
-          case "dblclick":
-            this.toggleFullscreen();
-            break;
-          case "resizevideocontrols":
-            this.adjustControlSize();
-            break;
-          // See comment at onFullscreenChange on bug 718107.
-          /*
-          case "fullscreenchange":
-            this.onFullscreenChange();
-            break;
-          */
-          case "keypress":
-            this.keyHandler(aEvent);
-            break;
-          case "dragstart":
-            aEvent.preventDefault(); // prevent dragging of controls image (bug 517114)
-            break;
-          case "input":
-            switch (aEvent.currentTarget) {
-              case this.scrubber:
-                this.onScrubberInput(aEvent);
-                break;
-              case this.volumeControl:
-                this.updateVolume();
-                break;
-            }
-            break;
-          case "change":
-            switch (aEvent.currentTarget) {
-              case this.scrubber:
-                this.onScrubberChange(aEvent);
-                break;
-              case this.video.textTracks:
-                this.setClosedCaptionButtonState();
-                break;
-            }
-            break;
-          case "mouseup":
-            // add mouseup listener additionally to handle the case that `change` event
-            // isn't fired when the input value before/after dragging are the same. (bug 1328061)
-            this.onScrubberChange(aEvent);
-            break;
-          case "addtrack":
-            this.onTextTrackAdd(aEvent);
-            break;
-          case "removetrack":
-            this.onTextTrackRemove(aEvent);
-            break;
-          case "media-videoCasting":
-            this.updateCasting(aEvent.detail);
-            break;
-          default:
-            this.log("!!! control event " + aEvent.type + " not handled!");
-        }
-      },
-
-      terminate() {
-        if (this.videoEvents) {
-          for (let event of this.videoEvents) {
-            try {
-              this.video.removeEventListener(event, this, {
-                capture: true,
-                mozSystemGroup: true,
-              });
-            } catch (ex) {}
-          }
-        }
-
-        try {
-          for (let { el, type, capture = false } of this.controlsEvents) {
-            el.removeEventListener(type, this, { mozSystemGroup: true, capture });
-          }
-        } catch (ex) {}
-
-        clearTimeout(this._showControlsTimeout);
-        clearTimeout(this._hideControlsTimeout);
-        this._cancelShowThrobberWhileResumingVideoDecoder();
-
-        this.log("--- videocontrols terminated ---");
-      },
-
-      hasError() {
-        // We either have an explicit error, or the resource selection
-        // algorithm is running and we've tried to load something and failed.
-        // Note: we don't consider the case where we've tried to load but
-        // there's no sources to load as an error condition, as sites may
-        // do this intentionally to work around requires-user-interaction to
-        // play restrictions, and we don't want to display a debug message
-        // if that's the case.
-        return this.video.error != null ||
-               (this.video.networkState == this.video.NETWORK_NO_SOURCE &&
-               this.hasSources());
-      },
-
-      hasSources() {
-        if (this.video.hasAttribute("src") &&
-            this.video.getAttribute("src") !== "") {
-          return true;
-        }
-        for (var child = this.video.firstChild;
-             child !== null;
-             child = child.nextElementSibling) {
-          if (child instanceof HTMLSourceElement) {
-            return true;
-          }
-        }
-        return false;
-      },
-
-      updateErrorText() {
-        let error;
-        let v = this.video;
-        // It is possible to have both v.networkState == NETWORK_NO_SOURCE
-        // as well as v.error being non-null. In this case, we will show
-        // the v.error.code instead of the v.networkState error.
-        if (v.error) {
-          switch (v.error.code) {
-            case v.error.MEDIA_ERR_ABORTED:
-              error = "errorAborted";
-              break;
-            case v.error.MEDIA_ERR_NETWORK:
-              error = "errorNetwork";
-              break;
-            case v.error.MEDIA_ERR_DECODE:
-              error = "errorDecode";
-              break;
-            case v.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
-              error = v.networkState == v.NETWORK_NO_SOURCE ?
-                "errorNoSource" :
-                "errorSrcNotSupported";
-              break;
-            default:
-              error = "errorGeneric";
-              break;
-          }
-        } else if (v.networkState == v.NETWORK_NO_SOURCE) {
-          error = "errorNoSource";
-        } else {
-          return; // No error found.
-        }
-
-        let label = document.getAnonymousElementByAttribute(this.videocontrols, "anonid", error);
-        this.controlsSpacer.setAttribute("aria-label", label.textContent);
-        this.statusOverlay.setAttribute("error", error);
-      },
-
-      formatTime(aTime, showHours = false) {
-        // Format the duration as "h:mm:ss" or "m:ss"
-        aTime = Math.round(aTime / 1000);
-        let hours = Math.floor(aTime / 3600);
-        let mins  = Math.floor((aTime % 3600) / 60);
-        let secs  = Math.floor(aTime % 60);
-        let timeString;
-        if (secs < 10) {
-          secs = "0" + secs;
-        }
-        if (hours || showHours) {
-          if (mins < 10) {
-            mins = "0" + mins;
-          }
-          timeString = hours + ":" + mins + ":" + secs;
-        } else {
-          timeString = mins + ":" + secs;
-        }
-        return timeString;
-      },
-
-      initPositionDurationBox() {
-        const positionTextNode = Array.prototype.find.call(
-          this.positionDurationBox.childNodes, (n) => !!~n.textContent.search("#1"));
-        const durationSpan = this.durationSpan;
-        const durationFormat = durationSpan.textContent;
-        const positionFormat = positionTextNode.textContent;
-
-        durationSpan.classList.add("duration");
-        durationSpan.setAttribute("role", "none");
-        durationSpan.setAttribute("anonid", "durationSpan");
-
-        Object.defineProperties(this.positionDurationBox, {
-          durationSpan: {
-            value: durationSpan,
-          },
-          position: {
-            set: (v) => {
-              positionTextNode.textContent = positionFormat.replace("#1", v);
-            },
-          },
-          duration: {
-            set: (v) => {
-              durationSpan.textContent = v ? durationFormat.replace("#2", v) : "";
-            },
-          },
-        });
-      },
-
-      showDuration(duration) {
-        let isInfinite = (duration == Infinity);
-        this.log("Duration is " + duration + "ms.\n");
-
-        if (isNaN(duration) || isInfinite) {
-          duration = this.maxCurrentTimeSeen;
-        }
-
-        // If the duration is over an hour, thumb should show h:mm:ss instead of mm:ss
-        this.showHours = (duration >= 3600000);
-
-        // Format the duration as "h:mm:ss" or "m:ss"
-        let timeString = isInfinite ? "" : this.formatTime(duration);
-        this.positionDurationBox.duration = timeString;
-
-        if (this.showHours) {
-          this.positionDurationBox.modifier = "long";
-          this.durationSpan.modifier = "long";
-        }
-
-        // "durationValue" property is used by scale binding to
-        // generate accessible name.
-        this.scrubber.durationValue = timeString;
-
-        this.scrubber.max = duration;
-        // XXX Can't set increment here, due to bug 473103. Also, doing so causes
-        // snapping when dragging with the mouse, so we can't just set a value for
-        // the arrow-keys.
-        this.scrubber.pageIncrement = Math.round(duration / 10);
-      },
-
-      pauseVideoDuringDragging() {
-        if (!this.video.paused &&
-            !this.isPausedByDragging &&
-            this.scrubber.isDragging) {
-          this.isPausedByDragging = true;
-          this.video.pause();
-        }
-      },
-
-      onScrubberInput(e) {
-        const duration = Math.round(this.video.duration * 1000); // in ms
-        let time = this.scrubber.value;
-
-        this.seekToPosition(time);
-        this.showPosition(time, duration);
-
-        this.scrubber.isDragging = true;
-        this.pauseVideoDuringDragging();
-      },
-
-      onScrubberChange(e) {
-        this.scrubber.isDragging = false;
-
-        if (this.isPausedByDragging) {
-          this.video.play();
-          this.isPausedByDragging = false;
-        }
-      },
-
-      updateScrubberProgress() {
-        const positionPercent = this.scrubber.value / this.scrubber.max * 100;
-
-        if (!isNaN(positionPercent) && positionPercent != Infinity) {
-          this.progressBar.value = positionPercent;
-        } else {
-          this.progressBar.value = 0;
-        }
-      },
-
-      seekToPosition(newPosition) {
-        newPosition /= 1000; // convert from ms
-        this.log("+++ seeking to " + newPosition);
-        this.video.currentTime = newPosition;
-      },
-
-      setVolume(newVolume) {
-        this.log("*** setting volume to " + newVolume);
-        this.video.volume = newVolume;
-        this.video.muted = false;
-      },
-
-      showPosition(currentTime, duration) {
-        // If the duration is unknown (because the server didn't provide
-        // it, or the video is a stream), then we want to fudge the duration
-        // by using the maximum playback position that's been seen.
-        if (currentTime > this.maxCurrentTimeSeen) {
-          this.maxCurrentTimeSeen = currentTime;
-        }
-        this.showDuration(duration);
-
-        this.log("time update @ " + currentTime + "ms of " + duration + "ms");
-
-        let positionTime = this.formatTime(currentTime, this.showHours);
-
-        this.scrubber.value = currentTime;
-        this.positionDurationBox.position = positionTime;
-        this.updateScrubberProgress();
-      },
-
-      showBuffered() {
-        function bsearch(haystack, needle, cmp) {
-          var length = haystack.length;
-          var low = 0;
-          var high = length;
-          while (low < high) {
-            var probe = low + ((high - low) >> 1);
-            var r = cmp(haystack, probe, needle);
-            if (r == 0) {
-              return probe;
-            } else if (r > 0) {
-              low = probe + 1;
-            } else {
-              high = probe;
-            }
-          }
-          return -1;
-        }
-
-        function bufferedCompare(buffered, i, time) {
-          if (time > buffered.end(i)) {
-            return 1;
-          } else if (time >= buffered.start(i)) {
-            return 0;
-          }
-          return -1;
-        }
-
-        var duration = Math.round(this.video.duration * 1000);
-        if (isNaN(duration) || duration == Infinity) {
-          duration = this.maxCurrentTimeSeen;
-        }
-
-        // Find the range that the current play position is in and use that
-        // range for bufferBar.  At some point we may support multiple ranges
-        // displayed in the bar.
-        var currentTime = this.video.currentTime;
-        var buffered = this.video.buffered;
-        var index = bsearch(buffered, currentTime, bufferedCompare);
-        var endTime = 0;
-        if (index >= 0) {
-          endTime = Math.round(buffered.end(index) * 1000);
-        }
-        this.bufferBar.max = duration;
-        this.bufferBar.value = endTime;
-      },
-
-      _controlsHiddenByTimeout: false,
-      _showControlsTimeout: 0,
-      SHOW_CONTROLS_TIMEOUT_MS: 500,
-      _showControlsFn() {
-        if (this.video.matches("video:hover")) {
-          this.startFadeIn(this.controlBar, false);
-          this._showControlsTimeout = 0;
-          this._controlsHiddenByTimeout = false;
-        }
-      },
-
-      _hideControlsTimeout: 0,
-      _hideControlsFn() {
-        if (!this.scrubber.isDragging) {
-          this.startFade(this.controlBar, false);
-          this._hideControlsTimeout = 0;
-          this._controlsHiddenByTimeout = true;
-        }
-      },
-      HIDE_CONTROLS_TIMEOUT_MS: 2000,
-      onMouseMove(event) {
-        // If the controls are static, don't change anything.
-        if (!this.dynamicControls) {
-          return;
-        }
-
-        clearTimeout(this._hideControlsTimeout);
-
-        // Suppress fading out the controls until the video has rendered
-        // its first frame. But since autoplay videos start off with no
-        // controls, let them fade-out so the controls don't get stuck on.
-        if (!this.firstFrameShown &&
-            !this.video.autoplay) {
-          return;
-        }
-
-        if (this._controlsHiddenByTimeout) {
-          this._showControlsTimeout =
-            setTimeout(() => this._showControlsFn(), this.SHOW_CONTROLS_TIMEOUT_MS);
-        } else {
-          this.startFade(this.controlBar, true);
-        }
-
-        // Hide the controls if the mouse cursor is left on top of the video
-        // but above the control bar and if the click-to-play overlay is hidden.
-        if ((this._controlsHiddenByTimeout ||
-            event.clientY < this.controlBar.getBoundingClientRect().top) &&
-            this.clickToPlay.hidden) {
-          this._hideControlsTimeout =
-            setTimeout(() => this._hideControlsFn(), this.HIDE_CONTROLS_TIMEOUT_MS);
-        }
-      },
-
-      onMouseInOut(event) {
-        // If the controls are static, don't change anything.
-        if (!this.dynamicControls) {
-          return;
-        }
-
-        clearTimeout(this._hideControlsTimeout);
-
-        // Ignore events caused by transitions between child nodes.
-        // Note that the videocontrols element is the same
-        // size as the *content area* of the video element,
-        // but this is not the same as the video element's
-        // border area if the video has border or padding.
-        if (this.checkEventWithin(event, this.videocontrols)) {
-          return;
-        }
-
-        var isMouseOver = (event.type == "mouseover");
-
-        var controlRect = this.controlBar.getBoundingClientRect();
-        var isMouseInControls = event.clientY > controlRect.top &&
-        event.clientY < controlRect.bottom &&
-        event.clientX > controlRect.left &&
-        event.clientX < controlRect.right;
-
-        // Suppress fading out the controls until the video has rendered
-        // its first frame. But since autoplay videos start off with no
-        // controls, let them fade-out so the controls don't get stuck on.
-        if (!this.firstFrameShown && !isMouseOver &&
-            !this.video.autoplay) {
-          return;
-        }
-
-        if (!isMouseOver && !isMouseInControls) {
-          this.adjustControlSize();
-
-          // Keep the controls visible if the click-to-play is visible.
-          if (!this.clickToPlay.hidden) {
-            return;
-          }
-
-          this.startFadeOut(this.controlBar, false);
-          this.textTrackList.hidden = true;
-          clearTimeout(this._showControlsTimeout);
-          this._controlsHiddenByTimeout = false;
-        }
-      },
-
-      startFadeIn(element, immediate) {
-        this.startFade(element, true, immediate);
-      },
-
-      startFadeOut(element, immediate) {
-        this.startFade(element, false, immediate);
-      },
-
-      animationMap: new WeakMap(),
-
-      animationProps: {
-        clickToPlay: {
-          keyframes: [
-            { transform: "scale(3)", opacity: 0 },
-            { transform: "scale(1)", opacity: 0.55 },
-          ],
-          options: {
-            easing: "ease",
-            duration: 400,
-            // The fill mode here and below is a workaround to avoid flicker
-            // due to bug 1495350.
-            fill: "both",
-          },
-        },
-        controlBar: {
-          keyframes: [
-            { opacity: 0 },
-            { opacity: 1 },
-          ],
-          options: {
-            easing: "ease",
-            duration: 200,
-            fill: "both",
-          },
-        },
-        statusOverlay: {
-          keyframes: [
-            { opacity: 0 },
-            { opacity: 0, offset: .72 }, // ~750ms into animation
-            { opacity: 1 },
-          ],
-          options: {
-            duration: 1050,
-            fill: "both",
-          },
-        },
-      },
-
-      startFade(element, fadeIn, immediate = false) {
-        // Bug 493523, the scrubber doesn't call valueChanged while hidden,
-        // so our dependent state (eg, timestamp in the thumb) will be stale.
-        // As a workaround, update it manually when it first becomes unhidden.
-        if (element == this.controlBar && fadeIn && element.hidden) {
-          this.scrubber.value = this.video.currentTime * 1000;
-        }
-
-        let animationProp =
-          this.animationProps[element.getAttribute("anonid")];
-        if (!animationProp) {
-          throw new Error("Element " + element.getAttribute("anonid") +
-            " has no transition. Toggle the hidden property directly.");
-        }
-
-        let animation = this.animationMap.get(element);
-        if (!animation) {
-          animation = new Animation(new KeyframeEffect(
-            element, animationProp.keyframes, animationProp.options));
-
-          this.animationMap.set(element, animation);
-        }
-
-        if (fadeIn) {
-          // hidden state should be controlled by adjustControlSize
-          if (element.isAdjustableControl && element.hiddenByAdjustment) {
-            return;
-          }
-
-          // No need to fade in again if the element is visible and not fading out
-          if (!element.hidden && !element.classList.contains("fadeout")) {
-            return;
-          }
-
-          if (element == this.controlBar) {
-            this.controlsSpacer.removeAttribute("hideCursor");
-          }
-
-          // Unhide
-          element.hidden = false;
-        } else {
-          // No need to fade out if the element is already no visible.
-          if (element.hidden) {
-            return;
-          }
-
-          if (element == this.controlBar && !this.hasError() &&
-              document.mozFullScreenElement == this.video) {
-            this.controlsSpacer.setAttribute("hideCursor", true);
-          }
-        }
-
-        element.classList.toggle("fadeout", !fadeIn);
-        element.classList.toggle("fadein", fadeIn);
-        let finishedPromise;
-        if (!immediate) {
-          // At this point, if there is a pending animation, we just stop it to avoid it happening.
-          // If there is a running animation, we reverse it, to have it rewind to the beginning.
-          // If there is an idle/finished animation, we schedule a new one that reverses the finished one.
-          if (animation.pending) {
-            // Animation is running but pending.
-            // Just cancel the pending animation to stop its effect.
-            animation.cancel();
-            finishedPromise = Promise.resolve();
-          } else {
-            switch (animation.playState) {
-              case "idle":
-              case "finished":
-                // There is no animation currently playing.
-                // Schedule a new animation with the desired playback direction.
-                animation.playbackRate = fadeIn ? 1 : -1;
-                animation.play();
-                break;
-              case "running":
-                // Allow the animation to play from its current position in
-                // reverse to finish.
-                animation.reverse();
-                break;
-              case "pause":
-              default:
-                throw new Error("Unknown Animation playState: " + animation.playState);
-            }
-            finishedPromise = animation.finished;
-          }
-        } else { // immediate
-          animation.cancel();
-          finishedPromise = Promise.resolve();
-        }
-        finishedPromise.then(animation => {
-          if (element == this.controlBar) {
-            this.onControlBarAnimationFinished();
-          }
-          element.classList.remove(fadeIn ? "fadein" : "fadeout");
-          if (!fadeIn) {
-            element.hidden = true;
-          }
-          if (animation) {
-            // Explicitly clear the animation effect so that filling animations
-            // stop overwriting stylesheet styles. Remove when bug 1495350 is
-            // fixed and animations are no longer filling animations.
-            // This also stops them from accumulating (See bug 1253476).
-            animation.cancel();
-          }
-        }, () => { /* Do nothing on rejection */ });
-      },
-
-      _triggeredByControls: false,
-
-      startPlay() {
-        this._triggeredByControls = true;
-        this.hideClickToPlay();
-        this.video.play();
-      },
-
-      togglePause() {
-        if (this.video.paused || this.video.ended) {
-          this.startPlay();
-        } else {
-          this.video.pause();
-        }
-
-        // We'll handle style changes in the event listener for
-        // the "play" and "pause" events, same as if content
-        // script was controlling video playback.
-      },
-
-      get isVideoWithoutAudioTrack() {
-        return this.video.readyState >= this.video.HAVE_METADATA &&
-               !this.isAudioOnly &&
-               !this.video.mozHasAudio;
-      },
-
-      toggleMute() {
-        if (this.isVideoWithoutAudioTrack) {
-          return;
-        }
-        this.video.muted = !this.isEffectivelyMuted;
-        if (this.video.volume === 0) {
-          this.video.volume = 0.5;
-        }
-
-        // We'll handle style changes in the event listener for
-        // the "volumechange" event, same as if content script was
-        // controlling volume.
-      },
-
-      get isVideoInFullScreen() {
-        return this.video.getRootNode().mozFullScreenElement == this.video;
-      },
-
-      toggleFullscreen() {
-        this.isVideoInFullScreen ?
-          document.mozCancelFullScreen() :
-          this.video.mozRequestFullScreen();
-      },
-
-      setFullscreenButtonState() {
-        if (this.isAudioOnly || !document.mozFullScreenEnabled) {
-          this.controlBar.setAttribute("fullscreen-unavailable", true);
-          this.adjustControlSize();
-          return;
-        }
-        this.controlBar.removeAttribute("fullscreen-unavailable");
-        this.adjustControlSize();
-
-        var attrName = this.isVideoInFullScreen ? "exitfullscreenlabel" : "enterfullscreenlabel";
-        var value = this.fullscreenButton.getAttribute(attrName);
-        this.fullscreenButton.setAttribute("aria-label", value);
-
-        if (this.isVideoInFullScreen) {
-          this.fullscreenButton.setAttribute("fullscreened", "true");
-        } else {
-          this.fullscreenButton.removeAttribute("fullscreened");
-        }
-      },
-
-      // XXX This should be the place where we update the control states and
-      // screen orientation upon entering/leaving fullscreen.
-      // Sadly because of bug 718107 as soon as this function exits
-      // the attached binding gets destructored and a new binding is then created.
-      // We therefore don't do anything here and leave it to the new binding to
-      // set state correctly from its constructor.
-      /*
-      onFullscreenChange() {
-        // Constructor and destructor will lock/unlock the orientation exactly
-        // once. Doing so here again will cause the videocontrols to
-        // lock-unlock-lock the orientation when entering the fullscreen.
-        this.updateOrientationState(this.isVideoInFullScreen);
-
-        // This is already broken by bug 718107 (controls will be hidden
-        // as soon as the video enters fullscreen).
-        // We can think about restoring the behavior here once the bug is
-        // fixed, or we could simply acknowledge the current behavior
-        // after-the-fact and try not to fix this.
-        if (this.isVideoInFullScreen) {
-          this._hideControlsTimeout =
-            setTimeout(() => this._hideControlsFn(), this.HIDE_CONTROLS_TIMEOUT_MS);
-        }
-
-        // Constructor will handle this correctly on the new DOM content in
-        // the new binding.
-        this.setFullscreenButtonState();
-      },
-      */
-
-      updateOrientationState(lock) {
-        if (!this.video.mozOrientationLockEnabled) {
-          return;
-        }
-        if (lock) {
-          if (this.video.mozIsOrientationLocked) {
-            return;
-          }
-          let dimenDiff = this.video.videoWidth - this.video.videoHeight;
-          if (dimenDiff > 0) {
-            this.video.mozIsOrientationLocked = window.screen.mozLockOrientation("landscape");
-          } else if (dimenDiff < 0) {
-            this.video.mozIsOrientationLocked = window.screen.mozLockOrientation("portrait");
-          } else {
-            this.video.mozIsOrientationLocked = window.screen.mozLockOrientation(window.screen.orientation);
-          }
-        } else {
-          if (!this.video.mozIsOrientationLocked) {
-            return;
-          }
-          window.screen.mozUnlockOrientation();
-          this.video.mozIsOrientationLocked = false;
-        }
-      },
-
-      clickToPlayClickHandler(e) {
-        if (e.button != 0) {
-          return;
-        }
-        if (this.hasError() && !this.suppressError) {
-          // Errors that can be dismissed should be placed here as we discover them.
-          if (this.video.error.code != this.video.error.MEDIA_ERR_ABORTED) {
-            return;
-          }
-          this.statusOverlay.hidden = true;
-          this.suppressError = true;
-          return;
-        }
-        if (e.defaultPrevented) {
-          return;
-        }
-        if (this.playButton.hasAttribute("paused")) {
-          this.startPlay();
-        } else {
-          this.video.pause();
-        }
-      },
-      hideClickToPlay() {
-        let videoHeight = this.video.clientHeight;
-        let videoWidth = this.video.clientWidth;
-
-        // The play button will animate to 3x its size. This
-        // shows the animation unless the video is too small
-        // to show 2/3 of the animation.
-        let animationScale = 2;
-        let animationMinSize = this.clickToPlay.minWidth * animationScale;
-
-        let immediate = (animationMinSize > videoWidth ||
-            animationMinSize > (videoHeight - this.controlBarMinHeight));
-        this.startFadeOut(this.clickToPlay, immediate);
-      },
-
-      setPlayButtonState(aPaused) {
-        if (aPaused) {
-          this.playButton.setAttribute("paused", "true");
-        } else {
-          this.playButton.removeAttribute("paused");
-        }
-
-        var attrName = aPaused ? "playlabel" : "pauselabel";
-        var value = this.playButton.getAttribute(attrName);
-        this.playButton.setAttribute("aria-label", value);
-      },
-
-      get isEffectivelyMuted() {
-        return this.video.muted || !this.video.volume;
-      },
-
-      updateMuteButtonState() {
-        var muted = this.isEffectivelyMuted;
-
-        if (muted) {
-          this.muteButton.setAttribute("muted", "true");
-        } else {
-          this.muteButton.removeAttribute("muted");
-        }
-
-        var attrName = muted ? "unmutelabel" : "mutelabel";
-        var value = this.muteButton.getAttribute(attrName);
-        this.muteButton.setAttribute("aria-label", value);
-      },
-
-      keyHandler(event) {
-        // Ignore keys when content might be providing its own.
-        if (!this.video.hasAttribute("controls")) {
-          return;
-        }
-
-        var keystroke = "";
-        if (event.altKey) {
-          keystroke += "alt-";
-        }
-        if (event.shiftKey) {
-          keystroke += "shift-";
-        }
-        if (navigator.platform.startsWith("Mac")) {
-          if (event.metaKey) {
-            keystroke += "accel-";
-          }
-          if (event.ctrlKey) {
-            keystroke += "control-";
-          }
-        } else {
-          if (event.metaKey) {
-            keystroke += "meta-";
-          }
-          if (event.ctrlKey) {
-            keystroke += "accel-";
-          }
-        }
-        switch (event.keyCode) {
-          case KeyEvent.DOM_VK_UP:
-            keystroke += "upArrow";
-            break;
-          case KeyEvent.DOM_VK_DOWN:
-            keystroke += "downArrow";
-            break;
-          case KeyEvent.DOM_VK_LEFT:
-            keystroke += "leftArrow";
-            break;
-          case KeyEvent.DOM_VK_RIGHT:
-            keystroke += "rightArrow";
-            break;
-          case KeyEvent.DOM_VK_HOME:
-            keystroke += "home";
-            break;
-          case KeyEvent.DOM_VK_END:
-            keystroke += "end";
-            break;
-        }
-
-        if (String.fromCharCode(event.charCode) == " ") {
-          keystroke += "space";
-        }
-
-        this.log("Got keystroke: " + keystroke);
-        var oldval, newval;
-
-        try {
-          switch (keystroke) {
-            case "space": /* Play */
-              let target = event.originalTarget;
-              if (target.localName === "button" && !target.disabled) {
-                break;
-              }
-
-              this.togglePause();
-              break;
-            case "downArrow": /* Volume decrease */
-              oldval = this.video.volume;
-              this.video.volume = (oldval < 0.1 ? 0 : oldval - 0.1);
-              this.video.muted = false;
-              break;
-            case "upArrow": /* Volume increase */
-              oldval = this.video.volume;
-              this.video.volume = (oldval > 0.9 ? 1 : oldval + 0.1);
-              this.video.muted = false;
-              break;
-            case "accel-downArrow": /* Mute */
-              this.video.muted = true;
-              break;
-            case "accel-upArrow": /* Unmute */
-              this.video.muted = false;
-              break;
-            case "leftArrow": /* Seek back 15 seconds */
-            case "accel-leftArrow": /* Seek back 10% */
-              oldval = this.video.currentTime;
-              if (keystroke == "leftArrow") {
-                newval = oldval - 15;
-              } else {
-                newval = oldval - (this.video.duration || this.maxCurrentTimeSeen / 1000) / 10;
-              }
-              this.video.currentTime = (newval >= 0 ? newval : 0);
-              break;
-            case "rightArrow": /* Seek forward 15 seconds */
-            case "accel-rightArrow": /* Seek forward 10% */
-              oldval = this.video.currentTime;
-              var maxtime = (this.video.duration || this.maxCurrentTimeSeen / 1000);
-              if (keystroke == "rightArrow") {
-                newval = oldval + 15;
-              } else {
-                newval = oldval + maxtime / 10;
-              }
-              this.video.currentTime = (newval <= maxtime ? newval : maxtime);
-              break;
-            case "home": /* Seek to beginning */
-              this.video.currentTime = 0;
-              break;
-            case "end": /* Seek to end */
-              if (this.video.currentTime != this.video.duration) {
-                this.video.currentTime = (this.video.duration || this.maxCurrentTimeSeen / 1000);
-              }
-              break;
-            default:
-              return;
-          }
-        } catch (e) { /* ignore any exception from setting .currentTime */ }
-
-        event.preventDefault(); // Prevent page scrolling
-      },
-
-      checkTextTrackSupport(textTrack) {
-        return textTrack.kind == "subtitles" ||
-               textTrack.kind == "captions";
-      },
-
-      get isCastingAvailable() {
-        return !this.isAudioOnly && this.video.mozAllowCasting;
-      },
-
-      get isClosedCaptionAvailable() {
-        return this.overlayableTextTracks.length;
-      },
-
-      get overlayableTextTracks() {
-        return Array.prototype.filter.call(this.video.textTracks, this.checkTextTrackSupport);
-      },
-
-      get currentTextTrackIndex() {
-        const showingTT = this.overlayableTextTracks.find(tt => tt.mode == "showing");
-
-        // fallback to off button if there's no showing track.
-        return showingTT ? showingTT.index : 0;
-      },
-
-      get isCastingOn() {
-        return this.isCastingAvailable && this.video.mozIsCasting;
-      },
-
-      setCastingButtonState() {
-        if (this.isCastingOn) {
-          this.castingButton.setAttribute("enabled", "true");
-        } else {
-          this.castingButton.removeAttribute("enabled");
-        }
-
-        this.adjustControlSize();
-      },
-
-      updateCasting(eventDetail) {
-        let castingData = JSON.parse(eventDetail);
-        if ("allow" in castingData) {
-          this.video.mozAllowCasting = !!castingData.allow;
-        }
-
-        if ("active" in castingData) {
-          this.video.mozIsCasting = !!castingData.active;
-        }
-        this.setCastingButtonState();
-      },
-
-      get isClosedCaptionOn() {
-        for (let tt of this.overlayableTextTracks) {
-          if (tt.mode === "showing") {
-            return true;
-          }
-        }
-
-        return false;
-      },
-
-      setClosedCaptionButtonState() {
-        if (this.isClosedCaptionOn) {
-          this.closedCaptionButton.setAttribute("enabled", "true");
-        } else {
-          this.closedCaptionButton.removeAttribute("enabled");
-        }
-
-        let ttItems = this.textTrackList.childNodes;
-
-        for (let tti of ttItems) {
-          const idx = +tti.getAttribute("index");
-
-          if (idx == this.currentTextTrackIndex) {
-            tti.setAttribute("on", "true");
-          } else {
-            tti.removeAttribute("on");
-          }
-        }
-
-        this.adjustControlSize();
-      },
-
-      addNewTextTrack(tt) {
-        if (!this.checkTextTrackSupport(tt)) {
-          return;
-        }
-
-        if (tt.index && tt.index < this.textTracksCount) {
-          // Don't create items for initialized tracks. However, we
-          // still need to care about mode since TextTrackManager would
-          // turn on the first available track automatically.
-          if (tt.mode === "showing") {
-            this.changeTextTrack(tt.index);
-          }
-          return;
-        }
-
-        tt.index = this.textTracksCount++;
-
-        const label = tt.label || "";
-        const ttText = document.createTextNode(label);
-        const ttBtn = document.createElement("button");
-
-        ttBtn.classList.add("textTrackItem");
-        ttBtn.setAttribute("index", tt.index);
-        ttBtn.appendChild(ttText);
-
-        this.textTrackList.appendChild(ttBtn);
-
-        if (tt.mode === "showing" && tt.index) {
-          this.changeTextTrack(tt.index);
-        }
-      },
-
-      changeTextTrack(index) {
-        for (let tt of this.overlayableTextTracks) {
-          if (tt.index === index) {
-            tt.mode = "showing";
-          } else {
-            tt.mode = "disabled";
-          }
-        }
-
-        this.textTrackList.hidden = true;
-      },
-
-      onControlBarAnimationFinished() {
-        this.textTrackList.hidden = true;
-        this.video.dispatchEvent(new CustomEvent("controlbarchange"));
-        this.adjustControlSize();
-      },
-
-      toggleCasting() {
-        this.videocontrols.dispatchEvent(new CustomEvent("VideoBindingCast"));
-      },
-
-      toggleClosedCaption() {
-        if (this.textTrackList.hidden) {
-          this.textTrackList.hidden = false;
-        } else {
-          this.textTrackList.hidden = true;
-        }
-      },
-
-      onTextTrackAdd(trackEvent) {
-        this.addNewTextTrack(trackEvent.track);
-        this.setClosedCaptionButtonState();
-      },
-
-      onTextTrackRemove(trackEvent) {
-        const toRemoveIndex = trackEvent.track.index;
-        const ttItems = this.textTrackList.childNodes;
-
-        if (!ttItems) {
-          return;
-        }
-
-        for (let tti of ttItems) {
-          const idx = +tti.getAttribute("index");
-
-          if (idx === toRemoveIndex) {
-            tti.remove();
-            this.textTracksCount--;
-          }
-
-          this.video.dispatchEvent(new CustomEvent("texttrackchange"));
-        }
-
-        this.setClosedCaptionButtonState();
-      },
-
-      initTextTracks() {
-        // add 'off' button anyway as new text track might be
-        // dynamically added after initialization.
-        const offLabel = this.textTrackList.getAttribute("offlabel");
-        this.addNewTextTrack({
-          label: offLabel,
-          kind: "subtitles",
-        });
-
-        for (let tt of this.overlayableTextTracks) {
-          this.addNewTextTrack(tt);
-        }
-
-        this.setClosedCaptionButtonState();
-      },
-
-      checkEventWithin(event, parent1, parent2) {
-        function isDescendant(node) {
-          while (node) {
-            if (node == parent1 || node == parent2) {
-              return true;
-            }
-            node = node.parentNode;
-          }
-          return false;
-        }
-        return isDescendant(event.target) && isDescendant(event.relatedTarget);
-      },
-
-      log(msg) {
-        if (this.debug) {
-          console.log("videoctl: " + msg + "\n");
-        }
-      },
-
-      get isTopLevelSyntheticDocument() {
-        let doc = this.video.ownerDocument;
-        let win = doc.defaultView;
-        return doc.mozSyntheticDocument && win === win.top;
-      },
-
-      controlBarMinHeight: 40,
-      controlBarMinVisibleHeight: 28,
-      adjustControlSize() {
-        const minControlBarPaddingWidth = 18;
-
-        this.fullscreenButton.isWanted = !this.controlBar.hasAttribute("fullscreen-unavailable");
-        this.castingButton.isWanted = this.isCastingAvailable;
-        this.closedCaptionButton.isWanted = this.isClosedCaptionAvailable;
-        this.volumeStack.isWanted = !this.muteButton.hasAttribute("noAudio");
-
-        let minRequiredWidth = this.prioritizedControls
-          .filter(control => control && control.isWanted)
-          .reduce((accWidth, cc) => accWidth + cc.minWidth, minControlBarPaddingWidth);
-        // Skip the adjustment in case the stylesheets haven't been loaded yet.
-        if (!minRequiredWidth) {
-          return;
-        }
-
-        let givenHeight = this.video.clientHeight;
-        let videoWidth = (this.isAudioOnly ?
-                          this.videocontrols.clientWidth :
-                          this.video.clientWidth) || minRequiredWidth;
-        let videoHeight = this.isAudioOnly ? this.controlBarMinHeight : givenHeight;
-        let videocontrolsWidth = this.videocontrols.clientWidth;
-
-        let widthUsed = minControlBarPaddingWidth;
-        let preventAppendControl = false;
-
-        for (let control of this.prioritizedControls) {
-          if (!control.isWanted) {
-            control.hiddenByAdjustment = true;
-            continue;
-          }
-
-          control.hiddenByAdjustment = preventAppendControl ||
-          widthUsed + control.minWidth > videoWidth;
-
-          if (control.hiddenByAdjustment) {
-            preventAppendControl = true;
-          } else {
-            widthUsed += control.minWidth;
-          }
-        }
-
-        // Use flexible spacer to separate controls when scrubber is hidden.
-        // As long as muteButton hidden, which means only play button presents,
-        // hide spacer and make playButton centered.
-        this.controlBarSpacer.hidden = !this.scrubberStack.hidden || this.muteButton.hidden;
-
-        // Since the size of videocontrols is expanded with controlBar in <audio>, we
-        // should fix the dimensions in order not to recursively trigger reflow afterwards.
-        if (this.video instanceof HTMLAudioElement) {
-          if (givenHeight) {
-            // The height of controlBar should be capped with the bounds between controlBarMinHeight
-            // and controlBarMinVisibleHeight.
-            let controlBarHeight = Math.max(Math.min(givenHeight, this.controlBarMinHeight), this.controlBarMinVisibleHeight);
-            this.controlBar.style.height = `${controlBarHeight}px`;
-          }
-          // Bug 1367875: Set minimum required width to controlBar if the given size is smaller than padding.
-          // This can help us expand the control and restore to the default size the next time we need
-          // to adjust the sizing.
-          if (videocontrolsWidth <= minControlBarPaddingWidth) {
-            this.controlBar.style.width = `${minRequiredWidth}px`;
-          } else {
-            this.controlBar.style.width = `${videoWidth}px`;
-          }
-          return;
-        }
-
-        if (videoHeight < this.controlBarMinHeight ||
-            widthUsed === minControlBarPaddingWidth) {
-          this.controlBar.setAttribute("size", "hidden");
-          this.controlBar.hiddenByAdjustment = true;
-        } else {
-          this.controlBar.removeAttribute("size");
-          this.controlBar.hiddenByAdjustment = false;
-        }
-
-        // Adjust clickToPlayButton size.
-        const minVideoSideLength = Math.min(videoWidth, videoHeight);
-        const clickToPlayViewRatio = 0.15;
-        const clickToPlayScaledSize = Math.max(
-        this.clickToPlay.minWidth, minVideoSideLength * clickToPlayViewRatio);
-
-        if (clickToPlayScaledSize >= videoWidth ||
-           (clickToPlayScaledSize + this.controlBarMinHeight / 2 >= videoHeight / 2 )) {
-          this.clickToPlay.hiddenByAdjustment = true;
-        } else {
-          if (this.clickToPlay.hidden && !this.video.played.length && this.video.paused) {
-            this.clickToPlay.hiddenByAdjustment = false;
-          }
-          this.clickToPlay.style.width = `${clickToPlayScaledSize}px`;
-          this.clickToPlay.style.height = `${clickToPlayScaledSize}px`;
-        }
-      },
-
-      init(binding) {
-        this.video = binding.parentNode;
-        this.videocontrols = binding;
-
-        this.controlsContainer    = document.getAnonymousElementByAttribute(binding, "anonid", "controlsContainer");
-        this.statusIcon    = document.getAnonymousElementByAttribute(binding, "anonid", "statusIcon");
-        this.controlBar    = document.getAnonymousElementByAttribute(binding, "anonid", "controlBar");
-        this.playButton    = document.getAnonymousElementByAttribute(binding, "anonid", "playButton");
-        this.controlBarSpacer    = document.getAnonymousElementByAttribute(binding, "anonid", "controlBarSpacer");
-        this.muteButton    = document.getAnonymousElementByAttribute(binding, "anonid", "muteButton");
-        this.volumeStack   = document.getAnonymousElementByAttribute(binding, "anonid", "volumeStack");
-        this.volumeControl = document.getAnonymousElementByAttribute(binding, "anonid", "volumeControl");
-        this.progressBar   = document.getAnonymousElementByAttribute(binding, "anonid", "progressBar");
-        this.bufferBar     = document.getAnonymousElementByAttribute(binding, "anonid", "bufferBar");
-        this.scrubberStack = document.getAnonymousElementByAttribute(binding, "anonid", "scrubberStack");
-        this.scrubber      = document.getAnonymousElementByAttribute(binding, "anonid", "scrubber");
-        this.durationLabel = document.getAnonymousElementByAttribute(binding, "anonid", "durationLabel");
-        this.positionLabel = document.getAnonymousElementByAttribute(binding, "anonid", "positionLabel");
-        this.positionDurationBox   = document.getAnonymousElementByAttribute(binding, "anonid", "positionDurationBox");
-        this.statusOverlay = document.getAnonymousElementByAttribute(binding, "anonid", "statusOverlay");
-        this.controlsOverlay = document.getAnonymousElementByAttribute(binding, "anonid", "controlsOverlay");
-        this.controlsSpacer     = document.getAnonymousElementByAttribute(binding, "anonid", "controlsSpacer");
-        this.clickToPlay        = document.getAnonymousElementByAttribute(binding, "anonid", "clickToPlay");
-        this.fullscreenButton   = document.getAnonymousElementByAttribute(binding, "anonid", "fullscreenButton");
-        this.castingButton = document.getAnonymousElementByAttribute(binding, "anonid", "castingButton");
-        this.closedCaptionButton = document.getAnonymousElementByAttribute(binding, "anonid", "closedCaptionButton");
-        this.textTrackList = document.getAnonymousElementByAttribute(binding, "anonid", "textTrackList");
-
-        if (this.positionDurationBox) {
-          this.durationSpan = this.positionDurationBox.getElementsByTagName("span")[0];
-        }
-
-        let isMobile = navigator.appVersion.includes("Android");
-        if (isMobile) {
-          this.controlsContainer.classList.add("mobile");
-        }
-
-        // TODO: Switch to touch controls on touch-based desktops (bug 1447547)
-        this.videocontrols.isTouchControls = isMobile;
-        if (this.videocontrols.isTouchControls) {
-          this.controlsContainer.classList.add("touch");
-        }
-
-        this.controlBarComputedStyles = getComputedStyle(this.controlBar);
-
-        // Hide and show control in certain order.
-        this.prioritizedControls = [
-          this.playButton,
-          this.muteButton,
-          this.fullscreenButton,
-          this.castingButton,
-          this.closedCaptionButton,
-          this.positionDurationBox,
-          this.scrubberStack,
-          this.durationSpan,
-          this.volumeStack,
-        ];
-
-        this.isAudioOnly = (this.video instanceof HTMLAudioElement);
-        this.setupInitialState();
-        this.setupNewLoadState();
-        this.initTextTracks();
-
-        // Use the handleEvent() callback for all media events.
-        // Only the "error" event listener must capture, so that it can trap error
-        // events from <source> children, which don't bubble. But we use capture
-        // for all events in order to simplify the event listener add/remove.
-        for (let event of this.videoEvents) {
-          this.video.addEventListener(event, this, {
-            capture: true,
-            mozSystemGroup: true,
-          });
-        }
-
-        this.controlsEvents = [
-          { el: this.muteButton, type: "click" },
-          { el: this.castingButton, type: "click" },
-          { el: this.closedCaptionButton, type: "click" },
-          { el: this.fullscreenButton, type: "click" },
-          { el: this.playButton, type: "click" },
-          { el: this.clickToPlay, type: "click" },
-
-          // On touch videocontrols, tapping controlsSpacer should show/hide
-          // the control bar, instead of playing the video or toggle fullscreen.
-          { el: this.controlsSpacer, type: "click", nonTouchOnly: true },
-          { el: this.controlsSpacer, type: "dblclick", nonTouchOnly: true },
-
-          { el: this.textTrackList, type: "click" },
-
-          { el: this.videocontrols, type: "resizevideocontrols" },
-
-          // See comment at onFullscreenChange on bug 718107.
-          // { el: this.video.ownerDocument, type: "fullscreenchange" },
-          { el: this.video, type: "keypress", capture: true },
-
-          // Prevent any click event within media controls from dispatching through to video.
-          { el: this.videocontrols, type: "click", mozSystemGroup: false },
-
-          // prevent dragging of controls image (bug 517114)
-          { el: this.videocontrols, type: "dragstart" },
-
-          { el: this.scrubber, type: "input" },
-          { el: this.scrubber, type: "change" },
-          // add mouseup listener additionally to handle the case that `change` event
-          // isn't fired when the input value before/after dragging are the same. (bug 1328061)
-          { el: this.scrubber, type: "mouseup" },
-          { el: this.volumeControl, type: "input" },
-          { el: this.video.textTracks, type: "addtrack" },
-          { el: this.video.textTracks, type: "removetrack" },
-          { el: this.video.textTracks, type: "change" },
-
-          { el: this.video, type: "media-videoCasting", touchOnly: true },
-        ];
-
-        for (let { el, type, nonTouchOnly = false, touchOnly = false,
-                   mozSystemGroup = true, capture = false } of this.controlsEvents) {
-          if ((this.videocontrols.isTouchControls && nonTouchOnly) ||
-              (!this.videocontrols.isTouchControls && touchOnly)) {
-            continue;
-          }
-          el.addEventListener(type, this, { mozSystemGroup, capture });
-        }
-
-        this.log("--- videocontrols initialized ---");
-      },
-    };
-
-    this.TouchUtils = {
-      videocontrols: null,
-      video: null,
-      controlsTimer: null,
-      controlsTimeout: 5000,
-
-      get Utils() {
-        return this.videocontrols.Utils;
-      },
-
-      get visible() {
-        return !this.Utils.controlBar.hasAttribute("fadeout") &&
-               !(this.Utils.controlBar.hidden);
-      },
-
-      firstShow: false,
-
-      toggleControls() {
-        if (!this.Utils.dynamicControls || !this.visible) {
-          this.showControls();
-        } else {
-          this.delayHideControls(0);
-        }
-      },
-
-      showControls() {
-        if (this.Utils.dynamicControls) {
-          this.Utils.startFadeIn(this.Utils.controlBar);
-          this.delayHideControls(this.controlsTimeout);
-        }
-      },
-
-      clearTimer() {
-        if (this.controlsTimer) {
-          clearTimeout(this.controlsTimer);
-          this.controlsTimer = null;
-        }
-      },
-
-      delayHideControls(aTimeout) {
-        this.clearTimer();
-        this.controlsTimer =
-          setTimeout(() => this.hideControls(), aTimeout);
-      },
-
-      hideControls() {
-        if (!this.Utils.dynamicControls) {
-          return;
-        }
-        this.Utils.startFadeOut(this.Utils.controlBar);
-      },
-
-      handleEvent(aEvent) {
-        switch (aEvent.type) {
-          case "click":
-            switch (aEvent.currentTarget) {
-              case this.Utils.playButton:
-                if (!this.video.paused) {
-                  this.delayHideControls(0);
-                } else {
-                  this.showControls();
-                }
-                break;
-              case this.Utils.muteButton:
-                this.delayHideControls(this.controlsTimeout);
-                break;
-            }
-            break;
-          case "touchstart":
-            this.clearTimer();
-            break;
-          case "touchend":
-            this.delayHideControls(this.controlsTimeout);
-            break;
-          case "mouseup":
-            if (aEvent.originalTarget == this.Utils.controlsSpacer) {
-              if (this.firstShow) {
-                this.Utils.video.play();
-                this.firstShow = false;
-              }
-              this.toggleControls();
-            }
-
-            break;
-        }
-
-        if (this.videocontrols.randomID != this.Utils.randomID) {
-          this.terminate();
-        }
-      },
-
-      terminate() {
-        try {
-          for (let { el, type, mozSystemGroup = true } of this.controlsEvents) {
-            el.removeEventListener(type, this, { mozSystemGroup });
-          }
-        } catch (ex) {}
-
-        this.clearTimer();
-      },
-
-      init(binding) {
-        this.videocontrols = binding;
-        this.video = binding.parentNode;
-
-        this.controlsEvents = [
-          { el: this.Utils.playButton, type: "click" },
-          { el: this.Utils.scrubber, type: "touchstart" },
-          { el: this.Utils.scrubber, type: "touchend" },
-          { el: this.Utils.muteButton, type: "click" },
-          { el: this.Utils.controlsSpacer, type: "mouseup" },
-        ];
-
-        for (let { el, type, mozSystemGroup = true } of this.controlsEvents) {
-          el.addEventListener(type, this, { mozSystemGroup });
-        }
-
-        // The first time the controls appear we want to just display
-        // a play button that does not fade away. The firstShow property
-        // makes that happen. But because of bug 718107 this init() method
-        // may be called again when we switch in or out of fullscreen
-        // mode. So we only set firstShow if we're not autoplaying and
-        // if we are at the beginning of the video and not already playing
-        if (!this.video.autoplay && this.Utils.dynamicControls && this.video.paused &&
-            this.video.currentTime === 0) {
-          this.firstShow = true;
-        }
-
-        // If the video is not at the start, then we probably just
-        // transitioned into or out of fullscreen mode, and we don't want
-        // the controls to remain visible. this.controlsTimeout is a full
-        // 5s, which feels too long after the transition.
-        if (this.video.currentTime !== 0) {
-          this.delayHideControls(this.Utils.HIDE_CONTROLS_TIMEOUT_MS);
-        }
-      },
-    };
-
-    this.Utils.init(this);
-    if (this.isTouchControls) {
-      this.TouchUtils.init(this);
-    }
-    this.dispatchEvent(new CustomEvent("VideoBindingAttached"));
-    ]]>
-  </constructor>
-  <destructor>
-    <![CDATA[
-    this.Utils.terminate();
-    this.TouchUtils.terminate();
-    this.Utils.updateOrientationState(false);
-    // randomID used to be a <field>, which meant that the XBL machinery
-    // undefined the property when the element was unbound. The code in
-    // this file actually depends on this, so now that randomID is an
-    // expando, we need to make sure to explicitly delete it.
-    delete this.randomID;
-    ]]>
-  </destructor>
-
-  </implementation>
-
-  <handlers>
-    <handler event="mouseover">
-      if (!this.isTouchControls) {
-        this.Utils.onMouseInOut(event);
-      }
-    </handler>
-    <handler event="mouseout">
-      if (!this.isTouchControls) {
-        this.Utils.onMouseInOut(event);
-      }
-    </handler>
-    <handler event="mousemove">
-      if (!this.isTouchControls) {
-        this.Utils.onMouseMove(event);
-      }
-    </handler>
-  </handlers>
-</binding>
-
-<binding id="noControls">
-
-  <resources>
-    <stylesheet src="chrome://global/skin/media/videocontrols.css"/>
-  </resources>
-
-  <xbl:content xmlns="http://www.w3.org/1999/xhtml" class="mediaControlsFrame">
-    <div anonid="controlsContainer" class="controlsContainer" role="none" hidden="true">
-      <div class="controlsOverlay stackItem">
-        <div class="controlsSpacerStack">
-          <div anonid="clickToPlay" class="clickToPlay"></div>
-        </div>
-      </div>
-    </div>
-  </xbl:content>
-
-  <implementation>
-  <constructor>
-    <![CDATA[
-    this.randomID = 0;
-    this.Utils = {
-      randomID: 0,
-      videoEvents: ["play",
-                    "playing",
-                    "MozNoControlsBlockedVideo"],
-      terminate() {
-        for (let event of this.videoEvents) {
-          try {
-            this.video.removeEventListener(event, this, {
-              capture: true,
-              mozSystemGroup: true,
-            });
-          } catch (ex) {}
-        }
-
-        try {
-          this.clickToPlay.removeEventListener("click", this, { mozSystemGroup: true });
-        } catch (ex) {}
-      },
-
-      hasError() {
-        return (this.video.error != null || this.video.networkState == this.video.NETWORK_NO_SOURCE);
-      },
-
-      handleEvent(aEvent) {
-        // If the binding is detached (or has been replaced by a
-        // newer instance of the binding), nuke our event-listeners.
-        if (this.videocontrols.randomID != this.randomID) {
-          this.terminate();
-          return;
-        }
-
-        switch (aEvent.type) {
-          case "play":
-            this.noControlsOverlay.hidden = true;
-            break;
-          case "playing":
-            this.noControlsOverlay.hidden = true;
-            break;
-          case "MozNoControlsBlockedVideo":
-            this.blockedVideoHandler();
-            break;
-          case "click":
-            this.clickToPlayClickHandler(aEvent);
-            break;
-        }
-      },
-
-      blockedVideoHandler() {
-        if (this.videocontrols.randomID != this.randomID) {
-          this.terminate();
-          return;
-        } else if (this.hasError()) {
-          this.noControlsOverlay.hidden = true;
-          return;
-        }
-        this.noControlsOverlay.hidden = false;
-      },
-
-      clickToPlayClickHandler(e) {
-        if (this.videocontrols.randomID != this.randomID) {
-          this.terminate();
-          return;
-        } else if (e.button != 0) {
-          return;
-        }
-
-        this.noControlsOverlay.hidden = true;
-        this.video.play();
-      },
-
-      init(binding) {
-        this.videocontrols = binding;
-        this.randomID = Math.random();
-        this.videocontrols.randomID = this.randomID;
-        this.video = binding.parentNode;
-        this.controlsContainer = document.getAnonymousElementByAttribute(binding, "anonid", "controlsContainer");
-        this.clickToPlay       = document.getAnonymousElementByAttribute(binding, "anonid", "clickToPlay");
-        this.noControlsOverlay = document.getAnonymousElementByAttribute(binding, "anonid", "controlsContainer");
-
-        let isMobile = navigator.appVersion.includes("Android");
-        if (isMobile) {
-          this.controlsContainer.classList.add("mobile");
-        }
-
-        // TODO: Switch to touch controls on touch-based desktops (bug 1447547)
-        this.videocontrols.isTouchControls = isMobile;
-        if (this.videocontrols.isTouchControls) {
-          this.controlsContainer.classList.add("touch");
-        }
-
-        this.clickToPlay.addEventListener("click", this, { mozSystemGroup: true });
-
-        for (let event of this.videoEvents) {
-          this.video.addEventListener(event, this, {
-            capture: true,
-            mozSystemGroup: true,
-          });
-        }
-      },
-    };
-    this.Utils.init(this);
-    this.Utils.video.dispatchEvent(new CustomEvent("MozNoControlsVideoBindingAttached"));
-    ]]>
-  </constructor>
-  <destructor>
-    <![CDATA[
-    this.Utils.terminate();
-    // randomID used to be a <field>, which meant that the XBL machinery
-    // undefined the property when the element was unbound. The code in
-    // this file actually depends on this, so now that randomID is an
-    // expando, we need to make sure to explicitly delete it.
-    delete this.randomID;
-    ]]>
-  </destructor>
-  </implementation>
-</binding>
-
-</bindings>
--- a/toolkit/themes/shared/media/videocontrols.css
+++ b/toolkit/themes/shared/media/videocontrols.css
@@ -1,17 +1,14 @@
 /* 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/. */
 
-@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace url("http://www.w3.org/1999/xhtml");
 
-video > xul|videocontrols,
-audio > xul|videocontrols,
 .videocontrols {
   writing-mode: horizontal-tb;
   width: 100%;
   height: 100%;
   display: inline-block;
   overflow: hidden;
 
   direction: ltr;
@@ -36,37 +33,37 @@ audio > xul|videocontrols,
   --button-size: 40px;
   --timer-size: 52px;
   --timer-long-size: 78px;
   --track-size: 7px;
   --thumb-size: 16px;
   --label-font-size: 16px;
 }
 
-/* Some CSS custom properties defined here are referenced by videocontrols.xml in JavaScript */
+/* Some CSS custom properties defined here are referenced by videocontrols.js */
 .controlBar {
   /* Do not delete: these variables are accessed by JavaScript directly.
-     see videocontrols.xml and search for |-width|. */
+     see videocontrols.js and search for |-width|. */
   --clickToPlay-width: var(--clickToPlay-size);
   --playButton-width: var(--button-size);
   --scrubberStack-width: 64px;
   --muteButton-width: var(--button-size);
   --volumeStack-width: 48px;
   --castingButton-width: var(--button-size);
   --closedCaptionButton-width: var(--button-size);
   --fullscreenButton-width: var(--button-size);
   --positionDurationBox-width: var(--timer-size);
   --durationSpan-width: var(--timer-size);
   --positionDurationBox-width-long: var(--timer-long-size);
   --durationSpan-width-long: var(--timer-long-size);
 }
 
 .touch .controlBar {
   /* Do not delete: these variables are accessed by JavaScript directly.
-     see videocontrols.xml and search for |-width|. */
+     see videocontrols.js and search for |-width|. */
   --scrubberStack-width: 84px;
   --volumeStack-width: 64px;
 }
 
 .controlsContainer [hidden],
 .controlBar[hidden] {
   display: none;
 }
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -1228,17 +1228,16 @@ STATIC_ATOMS = [
     Atom("var", "var"),
     Atom("variable", "variable"),
     Atom("vendor", "vendor"),
     Atom("vendorUrl", "vendor-url"),
     Atom("version", "version"),
     Atom("vertical", "vertical"),
     Atom("audio", "audio"),
     Atom("video", "video"),
-    Atom("videocontrols", "videocontrols"),
     Atom("viewport", "viewport"),
     Atom("viewport_height", "viewport-height"),
     Atom("viewport_initial_scale", "viewport-initial-scale"),
     Atom("viewport_maximum_scale", "viewport-maximum-scale"),
     Atom("viewport_minimum_scale", "viewport-minimum-scale"),
     Atom("viewport_user_scalable", "viewport-user-scalable"),
     Atom("viewport_width", "viewport-width"),
     Atom("visibility", "visibility"),