Bug 1175913 - (Part 1) Subscribe to EventListenerService and recreate accessibles on click listener changes r=tbsaunde
authorLorien Hu <lorien@lorienhu.com>
Tue, 04 Aug 2015 23:35:54 -0400
changeset 287920 89002bda1f558d5df20a36e0db4b1a5c10d517f8
parent 287919 ef291c49717330a7e9132b075a68baccbd2e8748
child 287921 6d03329ef85e157515b3262966c420cfe3bc98f6
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde
bugs1175913
milestone42.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 1175913 - (Part 1) Subscribe to EventListenerService and recreate accessibles on click listener changes r=tbsaunde
accessible/base/nsAccessibilityService.cpp
accessible/base/nsAccessibilityService.h
accessible/generic/DocAccessible.cpp
accessible/tests/mochitest/treeupdate/a11y.ini
accessible/tests/mochitest/treeupdate/test_bug1175913.html
accessible/tests/mochitest/treeupdate/test_textleaf.html
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -17,16 +17,17 @@
 #include "HTMLLinkAccessible.h"
 #include "HTMLListAccessible.h"
 #include "HTMLSelectAccessible.h"
 #include "HTMLTableAccessibleWrap.h"
 #include "HyperTextAccessibleWrap.h"
 #include "RootAccessible.h"
 #include "nsAccessiblePivot.h"
 #include "nsAccUtils.h"
+#include "nsArrayUtils.h"
 #include "nsAttrName.h"
 #include "nsEventShell.h"
 #include "nsIURI.h"
 #include "OuterDocAccessible.h"
 #include "Platform.h"
 #include "Role.h"
 #ifdef MOZ_ACCESSIBILITY_ATK
 #include "RootAccessibleWrap.h"
@@ -269,23 +270,82 @@ nsAccessibilityService::nsAccessibilityS
 
 nsAccessibilityService::~nsAccessibilityService()
 {
   NS_ASSERTION(gIsShutdown, "Accessibility wasn't shutdown!");
   gAccessibilityService = nullptr;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// nsIListenerChangeListener
+
+NS_IMETHODIMP
+nsAccessibilityService::ListenersChanged(nsIArray* aEventChanges)
+{
+  uint32_t targetCount;
+  nsresult rv = aEventChanges->GetLength(&targetCount);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  for (uint32_t i = 0 ; i < targetCount ; i++) {
+    nsCOMPtr<nsIEventListenerChange> change = do_QueryElementAt(aEventChanges, i);
+
+    nsCOMPtr<nsIDOMEventTarget> target;
+    change->GetTarget(getter_AddRefs(target));
+    nsCOMPtr<nsIContent> node(do_QueryInterface(target));
+    if (!node || !node->IsHTMLElement()) {
+      continue;
+    }
+    nsCOMPtr<nsIArray> listenerNames;
+    change->GetChangedListenerNames(getter_AddRefs(listenerNames));
+
+    uint32_t changeCount;
+    rv = listenerNames->GetLength(&changeCount);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    for (uint32_t i = 0 ; i < changeCount ; i++) {
+      nsCOMPtr<nsIAtom> listenerName = do_QueryElementAt(listenerNames, i);
+
+      // We are only interested in event listener changes which may
+      // make an element accessible or inaccessible.
+      if (listenerName != nsGkAtoms::onclick &&
+          listenerName != nsGkAtoms::onmousedown &&
+          listenerName != nsGkAtoms::onmouseup) {
+        continue;
+      }
+
+      nsIDocument* ownerDoc = node->OwnerDoc();
+      DocAccessible* document = GetExistingDocAccessible(ownerDoc);
+
+      // Always recreate for onclick changes.
+      if (document) {
+        if (nsCoreUtils::HasClickListener(node)) {
+          if (!document->GetAccessible(node)) {
+            document->RecreateAccessible(node);
+          }
+        } else {
+          if (document->GetAccessible(node)) {
+            document->RecreateAccessible(node);
+          }
+        }
+        break;
+      }
+    }
+  }
+  return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // nsISupports
 
 NS_IMPL_ISUPPORTS_INHERITED(nsAccessibilityService,
                             DocManager,
                             nsIAccessibilityService,
                             nsIAccessibleRetrieval,
                             nsIObserver,
+                            nsIListenerChangeListener,
                             nsISelectionListener) // from SelectionManager
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIObserver
 
 NS_IMETHODIMP
 nsAccessibilityService::Observe(nsISupports *aSubject, const char *aTopic,
                          const char16_t *aData)
@@ -1253,16 +1313,24 @@ nsAccessibilityService::Init()
   if (!observerService)
     return false;
 
   observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
 
   static const char16_t kInitIndicator[] = { '1', 0 };
   observerService->NotifyObservers(nullptr, "a11y-init-or-shutdown", kInitIndicator);
 
+  // Subscribe to EventListenerService.
+  nsCOMPtr<nsIEventListenerService> eventListenerService =
+    do_GetService("@mozilla.org/eventlistenerservice;1");
+  if (!eventListenerService)
+    return false;
+
+  eventListenerService->AddListenerChangeListener(this);
+
   for (uint32_t i = 0; i < ArrayLength(sMarkupMapList); i++)
     mMarkupMaps.Put(*sMarkupMapList[i].tag, &sMarkupMapList[i]);
 
 #ifdef A11Y_LOG
   logging::CheckEnv();
 #endif
 
   if (XRE_IsParentProcess())
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -10,18 +10,20 @@
 
 #include "mozilla/a11y/DocManager.h"
 #include "mozilla/a11y/FocusManager.h"
 #include "mozilla/a11y/Role.h"
 #include "mozilla/a11y/SelectionManager.h"
 #include "mozilla/Preferences.h"
 
 #include "nsIObserver.h"
+#include "nsIEventListenerService.h"
 
 class nsImageFrame;
+class nsIArray;
 class nsIPersistentProperties;
 class nsPluginFrame;
 class nsITreeView;
 
 namespace mozilla {
 namespace a11y {
 
 class ApplicationAccessible;
@@ -62,22 +64,26 @@ struct MarkupMapInfo {
 
 } // namespace a11y
 } // namespace mozilla
 
 class nsAccessibilityService final : public mozilla::a11y::DocManager,
                                      public mozilla::a11y::FocusManager,
                                      public mozilla::a11y::SelectionManager,
                                      public nsIAccessibilityService,
+                                     public nsIListenerChangeListener,
                                      public nsIObserver
 {
 public:
   typedef mozilla::a11y::Accessible Accessible;
   typedef mozilla::a11y::DocAccessible DocAccessible;
 
+  // nsIListenerChangeListener
+  NS_IMETHOD ListenersChanged(nsIArray* aEventChanges) override;
+
 protected:
   virtual ~nsAccessibilityService();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIACCESSIBLERETRIEVAL
   NS_DECL_NSIOBSERVER
 
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1606,18 +1606,17 @@ DocAccessible::UpdateAccessibleOnAttrCha
     // Recreate the accessible when role is changed because we might require a
     // different accessible class for the new role or the accessible may expose
     // a different sets of interfaces (COM restriction).
     RecreateAccessible(aElement);
 
     return true;
   }
 
-  if (aAttribute == nsGkAtoms::href ||
-      aAttribute == nsGkAtoms::onclick) {
+  if (aAttribute == nsGkAtoms::href) {
     // Not worth the expense to ensure which namespace these are in. It doesn't
     // kill use to recreate the accessible even if the attribute was used in
     // the wrong namespace or an element that doesn't support it.
 
     // Make sure the accessible is recreated asynchronously to allow the content
     // to handle the attribute change.
     RecreateAccessible(aElement);
     return true;
--- a/accessible/tests/mochitest/treeupdate/a11y.ini
+++ b/accessible/tests/mochitest/treeupdate/a11y.ini
@@ -2,16 +2,17 @@
 
 [test_ariadialog.html]
 [test_bug852150.xhtml]
 [test_bug883708.xhtml]
 [test_bug884251.xhtml]
 [test_bug895082.html]
 [test_bug1040735.html]
 [test_bug1100602.html]
+[test_bug1175913.html]
 [test_canvas.html]
 [test_colorpicker.xul]
 [test_contextmenu.xul]
 [test_cssoverflow.html]
 [test_deck.xul]
 [test_doc.html]
 [test_gencontent.html]
 [test_hidden.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/treeupdate/test_bug1175913.html
@@ -0,0 +1,103 @@
+<html>
+
+<head>
+  <title>Test hide/show events on event listener changes</title>
+  <link rel="stylesheet" type="text/css"
+        href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <script type="application/javascript"
+          src="../common.js"></script>
+  <script type="application/javascript"
+          src="../role.js"></script>
+  <script type="application/javascript"
+          src="../events.js"></script>
+
+  <script type="application/javascript">
+
+    function dummyListener() {}
+
+    function testAddListener()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_SHOW, getNode("parent")),
+      ];
+
+      this.invoke = function testAddListener_invoke()
+      {
+        is(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that parent is not accessible.");
+        is(getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that child is not accessible.");
+        getNode("parent").addEventListener("click", dummyListener);
+      }
+
+      this.finalCheck = function testAddListener_finalCheck()
+      {
+        var tree = { TEXT: [] };
+        testAccessibleTree("parent", tree);
+      }
+
+      this.getID = function testAddListener_getID()
+      {
+        return "Test that show event is sent when click listener is added";
+      }
+    }
+
+    function testRemoveListener()
+    {
+      this.eventSeq = [
+        new invokerChecker(EVENT_HIDE, getNode("parent")),
+      ];
+
+      this.invoke = function testRemoveListener_invoke()
+      {
+        getNode("parent").removeEventListener("click", dummyListener);
+      }
+
+      this.finalCheck = function testRemoveListener_finalCheck()
+      {
+        is(getAccessible("parent", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that parent is not accessible.");
+        is(getAccessible("child", null, null, DONOTFAIL_IF_NO_ACC), null, "Check that child is not accessible.");
+      }
+
+      this.getID = function testRemoveListener_getID()
+      {
+        return "Test that hide event is sent when click listener is removed";
+      }
+    }
+
+    var gQueue = null;
+    function doTest()
+    {
+      gQueue = new eventQueue();
+      gQueue.push(new testAddListener());
+      gQueue.push(new testRemoveListener());
+      gQueue.invoke(); // SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175913"
+     title="Crash in mozilla::a11y::DocAccessibleParent::RemoveAccessible(ProxyAccessible* aAccessible)">
+    Mozilla Bug 1175913
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <span id="parent">
+      <span id="child">
+      </span>
+  </span>
+
+</body>
+</html>
--- a/accessible/tests/mochitest/treeupdate/test_textleaf.html
+++ b/accessible/tests/mochitest/treeupdate/test_textleaf.html
@@ -35,27 +35,20 @@
         var textLeaf = getAccessible(this.node).firstChild;
         is(textLeaf.actionCount, (aIsTextLeafLinkable ? 1 : 0),
            "Wrong action numbers!");
       }
     }
 
     function setOnClickAttr(aID)
     {
-      this.__proto__ = new textLeafUpdate(aID, true);
-
-      this.invoke = function setOnClickAttr_invoke()
-      {
-        this.node.setAttribute("onclick", "alert(3);");
-      }
-
-      this.getID = function setOnClickAttr_getID()
-      {
-        return "make " + prettyName(aID) + " linkable";
-      }
+      var node = getNode(aID);
+      node.setAttribute("onclick", "alert(3);");
+      var textLeaf = getAccessible(node).firstChild;
+      is(textLeaf.actionCount, 1, "Wrong action numbers!");
     }
 
     function removeOnClickAttr(aID)
     {
       this.__proto__ = new textLeafUpdate(aID, false);
 
       this.invoke = function removeOnClickAttr_invoke()
       {
@@ -129,21 +122,20 @@
 
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
 
     var gQueue = null;
 
     function doTest()
     {
+      // adds onclick on element, text leaf should inherit its action
+      setOnClickAttr("div");
+      // Call rest of event tests.
       gQueue = new eventQueue();
-
-      // adds onclick on element, text leaf should inherit its action
-      gQueue.push(new setOnClickAttr("div"));
-
       // remove onclick attribute, text leaf shouldn't have any action
       gQueue.push(new removeOnClickAttr("div"));
 
       // set onclick attribute making span accessible, it's inserted into tree
       // and adopts text leaf accessible, text leaf should have an action
       gQueue.push(new setOnClickNRoleAttrs("span"));
 
       // text data removal of text node should remove its text accessible