Bug 483573 - Expose HTML5 video and audio elements' embedded controls through accessibility APIs, r=davidb, r=MarcoZ, sr=roc
authorAlexander Surkov <surkov.alexander@gmail.com>
Mon, 20 Apr 2009 09:09:21 +0200
changeset 27515 cb56ac9374812007c1af1d8a5caa93d73575da02
parent 27514 b5339dcad9d4b6f9d5b6b8547eb4871bde2c8541
child 27516 e33f765a54cf04d16e5900b88e4693c65f6e2b79
push id6588
push usermzehe@mozilla.com
push dateMon, 20 Apr 2009 07:09:49 +0000
treeherdermozilla-central@cb56ac937481 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdavidb, MarcoZ, roc
bugs483573
milestone1.9.2a1pre
Bug 483573 - Expose HTML5 video and audio elements' embedded controls through accessibility APIs, r=davidb, r=MarcoZ, sr=roc
accessible/public/nsIAccessibilityService.idl
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsAccessibleTreeWalker.cpp
accessible/tests/mochitest/Makefile.in
accessible/tests/mochitest/common.js
accessible/tests/mochitest/role.js
accessible/tests/mochitest/test_elm_filectrl.html
accessible/tests/mochitest/test_elm_media.html
layout/generic/nsVideoFrame.cpp
--- a/accessible/public/nsIAccessibilityService.idl
+++ b/accessible/public/nsIAccessibilityService.idl
@@ -41,17 +41,17 @@
 
 interface nsIAccessibleEventListener;
 interface nsIDocument;
 interface nsIFrame;
 interface nsObjectFrame;
 interface nsIContent;
 interface nsITimer;
 
-[uuid(44685af8-18be-494a-8e64-16c7d4296dd1)]
+[uuid(05481634-4700-45d6-8a0c-704f3a5abc00)]
 interface nsIAccessibilityService : nsIAccessibleRetrieval
 {
   nsIAccessible createOuterDocAccessible(in nsIDOMNode aNode);
   nsIAccessible createRootAccessible(in nsIPresShell aShell, in nsIDocument aDocument);
 
   nsIAccessible createHTML4ButtonAccessible(in nsIFrame aFrame);
   nsIAccessible createHyperTextAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLBRAccessible(in nsIFrame aFrame);
@@ -61,16 +61,17 @@ interface nsIAccessibilityService : nsIA
   nsIAccessible createHTMLCheckboxAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLComboboxAccessible(in nsIDOMNode aNode, in nsIWeakReference aPresShell);
   nsIAccessible createHTMLGenericAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLGroupboxAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLHRAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLImageAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLLabelAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLListboxAccessible(in nsIDOMNode aNode, in nsIWeakReference aPresShell);
+  nsIAccessible createHTMLMediaAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLObjectFrameAccessible(in nsObjectFrame aFrame);
   nsIAccessible createHTMLRadioButtonAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLSelectOptionAccessible(in nsIDOMNode aNode, in nsIAccessible aAccParent, in nsIWeakReference aPresShell);
   nsIAccessible createHTMLTableAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLTableCellAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLTableHeadAccessible(in nsIDOMNode aDOMNode);
   nsIAccessible createHTMLTextAccessible(in nsIFrame aFrame);
   nsIAccessible createHTMLTextFieldAccessible(in nsIFrame aFrame);
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -679,16 +679,37 @@ nsAccessibilityService::CreateHTMLListbo
   *_retval = new nsHTMLSelectListAccessible(aDOMNode, aPresShell);
   if (! *_retval) 
     return NS_ERROR_OUT_OF_MEMORY;
 
   NS_ADDREF(*_retval);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsAccessibilityService::CreateHTMLMediaAccessible(nsIFrame *aFrame,
+                                                  nsIAccessible **aAccessible)
+{
+  NS_ENSURE_ARG_POINTER(aAccessible);
+  *aAccessible = nsnull;
+
+  nsCOMPtr<nsIDOMNode> node;
+  nsCOMPtr<nsIWeakReference> weakShell;
+  nsresult rv = GetInfo(aFrame, getter_AddRefs(weakShell),
+                        getter_AddRefs(node));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aAccessible = new nsEnumRoleAccessible(node, weakShell,
+                                          nsIAccessibleRole::ROLE_GROUPING);
+  NS_ENSURE_TRUE(*aAccessible, NS_ERROR_OUT_OF_MEMORY);
+
+  NS_ADDREF(*aAccessible);
+  return NS_OK;
+}
+
 /**
   * We can have several cases here. 
   *  1) a text or html embedded document where the contentDocument
   *     variable in the object element holds the content
   *  2) web content that uses a plugin, which means we will
   *     have to go to the plugin to get the accessible content
   *  3) An image or imagemap, where the image frame points back to 
   *     the object element DOMNode
--- a/accessible/src/base/nsAccessibleTreeWalker.cpp
+++ b/accessible/src/base/nsAccessibleTreeWalker.cpp
@@ -32,18 +32,21 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsAccessibleTreeWalker.h"
+
 #include "nsAccessibilityAtoms.h"
 #include "nsAccessNode.h"
+
+#include "nsIAnonymousContentCreator.h"
 #include "nsIServiceManager.h"
 #include "nsIContent.h"
 #include "nsIDOMXULElement.h"
 #include "nsIPresShell.h"
 #include "nsIFrame.h"
 #include "nsWeakReference.h"
 
 nsAccessibleTreeWalker::nsAccessibleTreeWalker(nsIWeakReference* aPresShell, nsIDOMNode* aNode, PRBool aWalkAnonContent): 
@@ -224,19 +227,27 @@ NS_IMETHODIMP nsAccessibleTreeWalker::Ge
   return NS_ERROR_FAILURE;
 }
 
 void nsAccessibleTreeWalker::UpdateFrame(PRBool aTryFirstChild)
 {
   if (!mState.frame) {
     return;
   }
+
   if (aTryFirstChild) {
-    nsIContent *containerContent = mState.frame->GetContent();
+    // If the frame implements nsIAnonymousContentCreator interface then go down
+    // through the frames and obtain anonymous nodes for them.
+    nsIAnonymousContentCreator* creator = do_QueryFrame(mState.frame);
     mState.frame = mState.frame->GetFirstChild(nsnull);
+
+    if (creator && mState.frame && mState.siblingIndex < 0) {
+      mState.domNode = do_QueryInterface(mState.frame->GetContent());
+      mState.siblingIndex = eSiblingsWalkFrames;
+    }
 // temporary workaround for Bug 359210. We never want to walk frames.
 // Aaron Leventhal will refix :before and :after content later without walking frames.
 #if 0
     if (mState.frame && mState.siblingIndex < 0) {
       // Container frames can contain generated content frames from
       // :before and :after style rules, so we walk their frame trees
       // instead of content trees
       // XXX Walking the frame tree doesn't get us Aural CSS nodes, e.g. 
@@ -249,27 +260,16 @@ void nsAccessibleTreeWalker::UpdateFrame
       // nsStyleContext *styleContext = primaryFrame->GetStyleContext();
       // if (aContent) {
       //   pseudoContext = presContext->StyleSet()->
       //     ProbePseudoStyleFor(content, nsAccessibilityAtoms::after, aStyleContext);
       mState.domNode = do_QueryInterface(mState.frame->GetContent());
       mState.siblingIndex = eSiblingsWalkFrames;
     }
 #endif
-    // Special case: <input type="file">
-    // We should still need to walk frames inside the file control frame
-    // This special case may turn into a more general rule after Firefox 3,
-    // if HTML 5 controls use nsIAnonymousContentCreator
-    if (containerContent->Tag() == nsAccessibilityAtoms::input &&
-        containerContent->AttrValueIs(kNameSpaceID_None, nsAccessibilityAtoms::type,
-                                      NS_LITERAL_STRING("file"), eIgnoreCase) &&
-        mState.frame && mState.siblingIndex < 0)  {
-      mState.domNode = do_QueryInterface(mState.frame->GetContent());
-      mState.siblingIndex = eSiblingsWalkFrames;
-    }
   }
   else {
     mState.frame = mState.frame->GetNextSibling();
   }
 }
 
 /**
  * If the DOM node's frame has an accessible or the DOMNode
--- a/accessible/tests/mochitest/Makefile.in
+++ b/accessible/tests/mochitest/Makefile.in
@@ -43,16 +43,17 @@ VPATH		= @srcdir@
 relativesrcdir  = accessible
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES =\
 		letters.gif \
 		moz.png \
+		$(topsrcdir)/content/media/video/test/bug461281.ogg \
 		longdesc_src.html \
 		attributes.js \
 		common.js \
 		events.js \
 		layout.js \
 		namerules.xml \
 		nsIAccessible_actions.js \
 		nsIAccessible_name.css \
@@ -69,16 +70,18 @@ include $(topsrcdir)/config/rules.mk
 		test_actions.xul \
 		test_aria_activedescendant.html \
 		test_aria_role_article.html \
 		test_aria_role_equation.html \
 		test_aria_token_attrs.html \
 		test_bug420863.html \
 	$(warning	test_childAtPoint.xul temporarily disabled) \
 		test_cssattrs.html \
+		test_elm_filectrl.html \
+		test_elm_media.html \
 		test_events_caretmove.html \
 		test_events_mutation.html \
 		test_events_tree.xul \
 		test_groupattrs.xul \
 		test_groupattrs.html \
 		test_name_markup.html \
 	$(warning test_table_indexes.html temporarily disabled) \
 		test_nsIAccessible_applicationAccessible.html \
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -261,16 +261,48 @@ function ensureAccessibleTree(aAccOrElmO
       child = child.nextSibling;
     } catch (e) {
       child = null;
     }
   }
 }
 
 /**
+ * Compare expected and actual accessibles trees.
+ */
+function testAccessibleTree(aAccOrElmOrID, aAccTree)
+{
+  var acc = getAccessible(aAccOrElmOrID);
+  if (!acc)
+    return;
+
+  for (var prop in aAccTree) {
+    var msg = "Wrong value of property '" + prop + "'.";
+    if (prop == "role")
+      is(roleToString(acc[prop]), roleToString(aAccTree[prop]), msg);
+    else if (prop != "children")
+      is(acc[prop], aAccTree[prop], msg);
+  }
+
+  if ("children" in aAccTree) {
+    var children = acc.children;
+    is(aAccTree.children.length, children.length,
+       "Different amount of expected children.");
+
+    if (aAccTree.children.length == children.length) { 
+      for (var i = 0; i < children.length; i++) {
+        var child = children.queryElementAt(i, nsIAccessible);
+        testAccessibleTree(child, aAccTree.children[i]);
+      }
+    }
+  }
+}
+
+
+/**
  * Convert role to human readable string.
  */
 function roleToString(aRole)
 {
   return gAccRetrieval.getStringRole(aRole);
 }
 
 /**
--- a/accessible/tests/mochitest/role.js
+++ b/accessible/tests/mochitest/role.js
@@ -1,13 +1,12 @@
 ////////////////////////////////////////////////////////////////////////////////
 // Role constants
 
 const ROLE_ALERT = nsIAccessibleRole.ROLE_ALERT;
-const ROLE_PUSHBUTTON = nsIAccessibleRole.ROLE_PUSHBUTTON;
 const ROLE_CELL = nsIAccessibleRole.ROLE_CELL;
 const ROLE_CHROME_WINDOW = nsIAccessibleRole.ROLE_CHROME_WINDOW;
 const ROLE_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX;
 const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST;
 const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION;
 const ROLE_DOCUMENT = nsIAccessibleRole.ROLE_DOCUMENT;
 const ROLE_ENTRY = nsIAccessibleRole.ROLE_ENTRY;
 const ROLE_FLAT_EQUATION = nsIAccessibleRole.ROLE_FLAT_EQUATION;
@@ -21,17 +20,20 @@ const ROLE_INTERNAL_FRAME = nsIAccessibl
 const ROLE_LABEL = nsIAccessibleRole.ROLE_LABEL;
 const ROLE_LINK = nsIAccessibleRole.ROLE_LINK;
 const ROLE_LIST = nsIAccessibleRole.ROLE_LIST;
 const ROLE_LISTBOX = nsIAccessibleRole.ROLE_LISTBOX;
 const ROLE_NOTHING = nsIAccessibleRole.ROLE_NOTHING;
 const ROLE_OPTION = nsIAccessibleRole.ROLE_OPTION;
 const ROLE_PARAGRAPH = nsIAccessibleRole.ROLE_PARAGRAPH;
 const ROLE_PASSWORD_TEXT = nsIAccessibleRole.ROLE_PASSWORD_TEXT;
+const ROLE_PROGRESSBAR = nsIAccessibleRole.ROLE_PROGRESSBAR;
+const ROLE_PUSHBUTTON = nsIAccessibleRole.ROLE_PUSHBUTTON;
 const ROLE_SECTION = nsIAccessibleRole.ROLE_SECTION;
+const ROLE_SLIDER = nsIAccessibleRole.ROLE_SLIDER;
 const ROLE_TABLE = nsIAccessibleRole.ROLE_TABLE;
 const ROLE_TEXT_CONTAINER = nsIAccessibleRole.ROLE_TEXT_CONTAINER;
 const ROLE_TEXT_LEAF = nsIAccessibleRole.ROLE_TEXT_LEAF;
 const ROLE_TOGGLE_BUTTON = nsIAccessibleRole.ROLE_TOGGLE_BUTTON;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Public methods
 
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/test_elm_filectrl.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483573
+-->
+<head>
+  <title>File Input Control tests</title>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/a11y/accessible/common.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/a11y/accessible/role.js"></script>
+
+  <script type="application/javascript">
+    function doTest()
+    {
+      var accTree = {
+        role: ROLE_TEXT_CONTAINER,
+        children: [
+          {
+            role: ROLE_ENTRY
+          },
+          {
+            role: ROLE_PUSHBUTTON
+          }
+        ]
+      };
+      testAccessibleTree("filectrl", accTree);
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+     title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <input type="file" id="filectrl" />
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/test_elm_media.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=483573
+-->
+<head>
+  <title>HTML5 audio/video tests</title>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/a11y/accessible/common.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/a11y/accessible/role.js"></script>
+
+  <script type="application/javascript">
+    function doTest()
+    {
+      var accTree = {
+        role: ROLE_GROUPING,
+        children: [
+          { // start/stop button
+            role: ROLE_PUSHBUTTON
+          },
+          { // buffer bar
+            role: ROLE_PROGRESSBAR
+          },
+          { // progress bar
+            role: ROLE_PROGRESSBAR
+          },
+          { // slider of progress bar
+            role: ROLE_SLIDER
+          },
+          { // volume button
+            role: ROLE_PUSHBUTTON
+          }
+        ]
+      };
+      testAccessibleTree("audio", accTree);
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+     title="Expose HTML5 video and audio elements' embedded controls through accessibility APIs"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=483573">Mozilla Bug 483573</a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <audio id="audio" src="chrome://mochikit/content/a11y/accessible/bug461281.ogg"
+         autoplay="true" controls="true">
+</body>
+</html>
--- a/layout/generic/nsVideoFrame.cpp
+++ b/layout/generic/nsVideoFrame.cpp
@@ -51,16 +51,22 @@
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
 #include "nsPresContext.h"
 #include "nsTransform2D.h"
 #include "nsContentCreatorFunctions.h"
 #include "nsBoxLayoutState.h"
 #include "nsBoxFrame.h"
 
+#ifdef ACCESSIBILITY
+#include "nsIServiceManager.h"
+#include "nsIAccessible.h"
+#include "nsIAccessibilityService.h"
+#endif
+
 nsIFrame*
 NS_NewHTMLVideoFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsVideoFrame(aContext);
 }
 
 nsVideoFrame::nsVideoFrame(nsStyleContext* aContext) :
   nsContainerFrame(aContext)
@@ -244,17 +250,22 @@ nsVideoFrame::GetType() const
 {
   return nsGkAtoms::HTMLVideoFrame;
 }
 
 #ifdef ACCESSIBILITY
 NS_IMETHODIMP
 nsVideoFrame::GetAccessible(nsIAccessible** aAccessible)
 {
-  return NS_ERROR_FAILURE;
+  nsCOMPtr<nsIAccessibilityService> accService =
+    do_GetService("@mozilla.org/accessibilityService;1");
+  NS_ENSURE_STATE(accService);
+
+  return accService->CreateHTMLMediaAccessible(static_cast<nsIFrame*>(this),
+                                               aAccessible);
 }
 #endif
 
 #ifdef DEBUG
 NS_IMETHODIMP
 nsVideoFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("HTMLVideo"), aResult);