Bug 1451241 - check text children of non-accessible nodes when retrieving accessible objects. r=jdescottes
authorYura Zenevich <yura.zenevich@gmail.com>
Fri, 13 Apr 2018 13:16:53 -0400
changeset 469186 71105dc996c9d7d8b4e4c7f9cc1a72559c6e120b
parent 469185 83d7c4d3b22724c231fd2a7ae0b3173f2467f532
child 469187 257469dad07cd5b3855513268e0956ae620128af
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1451241
milestone61.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 1451241 - check text children of non-accessible nodes when retrieving accessible objects. r=jdescottes MozReview-Commit-ID: 2SFe6g3hOCc
devtools/client/accessibility/accessibility-view.js
devtools/client/accessibility/test/browser_accessibility_context_menu_inspector.js
devtools/client/inspector/inspector.js
devtools/server/actors/inspector/walker.js
--- a/devtools/client/accessibility/accessibility-view.js
+++ b/devtools/client/accessibility/accessibility-view.js
@@ -1,14 +1,16 @@
 /* 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/. */
 "use strict";
 
-/* global EVENTS */
+/* global EVENTS, gToolbox */
+
+const nodeConstants = require("devtools/shared/dom-node-constants");
 
 // React & Redux
 const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 const { combineReducers } = require("devtools/client/shared/vendor/redux");
 
 // Accessibility Panel
@@ -68,17 +70,32 @@ AccessibilityView.prototype = {
   },
 
   async highlightAccessible(walker, accessible) {
     await this.store.dispatch(highlight(walker, accessible));
     window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED);
   },
 
   async selectNodeAccessible(walker, node) {
-    const accessible = await walker.getAccessibleFor(node);
+    let accessible = await walker.getAccessibleFor(node);
+    // If node does not have an accessible object, try to find node's child text node and
+    // try to retrieve an accessible object for that child instead. This is the best
+    // effort approach until there's accessibility API to retrieve accessible object at
+    // point.
+    if (!accessible || accessible.indexInParent < 0) {
+      const { nodes: children } = await gToolbox.walker.children(node);
+      for (let child of children) {
+        if (child.nodeType === nodeConstants.TEXT_NODE) {
+          accessible = await walker.getAccessibleFor(child);
+          if (accessible && accessible.indexInParent >= 0) {
+            break;
+          }
+        }
+      }
+    }
 
     await this.store.dispatch(select(walker, accessible));
     window.emit(EVENTS.NEW_ACCESSIBLE_FRONT_HIGHLIGHTED);
   },
 
   /**
    * Process message from accessibility panel.
    *
--- a/devtools/client/accessibility/test/browser_accessibility_context_menu_inspector.js
+++ b/devtools/client/accessibility/test/browser_accessibility_context_menu_inspector.js
@@ -1,43 +1,83 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/publicdomain/zero/1.0/ */
 "use strict";
 
-const TEST_URI = "<h1 id=\"h1\">header</h1><p id=\"p\">paragraph</p>";
+const TEST_URI = `
+  <h1 id="h1">header</h1>
+  <p id="p">paragraph</p>
+  <span id="span-1">text</span>
+  <span id="span-2">
+    IamaverylongtextwhichdoesntfitinInlineTextChildReallyIdontIamtoobig
+  </span>`;
+
+async function openContextMenuForNode({ toolbox }, selector) {
+  info("Selecting Inspector tab and opening a context menu");
+  const inspector = await toolbox.selectTool("inspector");
+
+  if (!selector) {
+    ok(inspector.selection.isBodyNode(), "Default selection is a body node.");
+  } else if (typeof selector === "string") {
+    await selectNode(selector, inspector, "test");
+  } else {
+    let updated = inspector.once("inspector-updated");
+    inspector.selection.setNodeFront(selector, { reason: "test" });
+    await updated;
+  }
+
+  let menuUpdated = inspector.once("node-menu-updated");
+  let allMenuItems = openContextMenuAndGetAllItems(inspector);
+  await menuUpdated;
+  return allMenuItems;
+}
+
+function checkShowA11YPropertiesNode(allMenuItems, disabled) {
+  let showA11YPropertiesNode = allMenuItems.find(item =>
+    item.id === "node-menu-showaccessibilityproperties");
+  ok(showA11YPropertiesNode,
+    "the popup menu now has a show accessibility properties item");
+  is(showA11YPropertiesNode.disabled, disabled,
+    "Show accessibility properties item has correct state");
+  return showA11YPropertiesNode;
+}
+
+async function checkAccessibleObjectSelection({ toolbox, panel }, menuItem, isText) {
+  const inspector = await toolbox.getPanel("inspector");
+  info("Triggering 'Show Accessibility Properties' and waiting for " +
+       "accessibility panel to open");
+  let panelSelected = toolbox.once("accessibility-selected");
+  let objectSelected = panel.once("new-accessible-front-selected");
+  menuItem.click();
+  await panelSelected;
+  let selected = await objectSelected;
+
+  let expectedNode = isText ?
+    inspector.selection.nodeFront.inlineTextChild : inspector.selection.nodeFront;
+  let expectedSelected = await panel.walker.getAccessibleFor(expectedNode);
+  is(selected, expectedSelected, "Accessible front selected correctly");
+}
 
 addA11YPanelTask("Test show accessibility properties context menu.", TEST_URI,
-  async function testShowAccessibilityPropertiesContextMenu({ panel, toolbox }) {
-    let inspector = await toolbox.selectTool("inspector");
+  async function testShowAccessibilityPropertiesContextMenu(env) {
+    let allMenuItems = await openContextMenuForNode(env);
+    let showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems, true);
 
-    ok(inspector.selection.isBodyNode(), "Default selection is a body node.");
-    let menuUpdated = inspector.once("node-menu-updated");
-    let allMenuItems = openContextMenuAndGetAllItems(inspector);
-    let showA11YPropertiesNode = allMenuItems.find(item =>
-      item.id === "node-menu-showaccessibilityproperties");
-    ok(showA11YPropertiesNode,
-      "the popup menu now has a show accessibility properties item");
-    await menuUpdated;
-    ok(showA11YPropertiesNode.disabled, "Body node does not have accessible");
+    allMenuItems = await openContextMenuForNode(env, "#h1");
+    showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems, false);
+    await checkAccessibleObjectSelection(env, showA11YPropertiesNode);
+
+    allMenuItems = await openContextMenuForNode(env, "#span-1");
+    showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems, false);
+    await checkAccessibleObjectSelection(env, showA11YPropertiesNode, true);
 
-    await selectNode("#h1", inspector, "test");
-    menuUpdated = inspector.once("node-menu-updated");
-    allMenuItems = openContextMenuAndGetAllItems(inspector);
-    showA11YPropertiesNode = allMenuItems.find(item =>
-      item.id === "node-menu-showaccessibilityproperties");
-    ok(showA11YPropertiesNode,
-      "the popup menu now has a show accessibility properties item");
-    await menuUpdated;
-    ok(!showA11YPropertiesNode.disabled, "Body node has an accessible");
+    allMenuItems = await openContextMenuForNode(env, "#span-2");
+    showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems, true);
 
-    info("Triggering 'Show Accessibility Properties' and waiting for " +
-         "accessibility panel to open");
-    let panelSelected = toolbox.once("accessibility-selected");
-    let objectSelected = panel.once("new-accessible-front-selected");
-    showA11YPropertiesNode.click();
-    await panelSelected;
-    let selected = await objectSelected;
-
-    let expectedSelected = await panel.walker.getAccessibleFor(
-      inspector.selection.nodeFront);
-    is(selected, expectedSelected, "Accessible front selected correctly");
+    const inspector = env.toolbox.getPanel("inspector");
+    let span2 = await getNodeFront("#span-2", inspector);
+    await inspector.markup.expandNode(span2);
+    let { nodes } = await inspector.walker.children(span2);
+    allMenuItems = await openContextMenuForNode(env, nodes[0]);
+    showA11YPropertiesNode = checkShowA11YPropertiesNode(allMenuItems, false);
+    await checkAccessibleObjectSelection(env, showA11YPropertiesNode, false);
   });
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1588,17 +1588,17 @@ Inspector.prototype = {
       menu.append(menuitem);
     }
 
     menu.popup(screenX, screenY, this._toolbox);
     return menu;
   },
 
   buildA11YMenuItem: function(menu) {
-    if (!this.selection.isElementNode() ||
+    if (!(this.selection.isElementNode() || this.selection.isTextNode()) ||
         !Services.prefs.getBoolPref("devtools.accessibility.enabled")) {
       return;
     }
 
     const showA11YPropsItem = new MenuItem({
       id: "node-menu-showaccessibilityproperties",
       label: INSPECTOR_L10N.getStr("inspectorShowAccessibilityProperties.label"),
       click: () => this.showAccessibilityProperties(),
--- a/devtools/server/actors/inspector/walker.js
+++ b/devtools/server/actors/inspector/walker.js
@@ -2018,14 +2018,23 @@ var WalkerActor = protocol.ActorClassWit
    */
   hasAccessibilityProperties: async function(node) {
     if (isNodeDead(node) || !Services.appinfo.accessibilityEnabled) {
       return false;
     }
 
     const accService = Cc["@mozilla.org/accessibilityService;1"].getService(
       Ci.nsIAccessibilityService);
-    const acc = accService.getAccessibleFor(node.rawNode);
+    let acc = accService.getAccessibleFor(node.rawNode);
+    // If node does not have an accessible object, but has an inline text child,
+    // try to retrieve an accessible object for the child instead.
+    if (!acc || acc.indexInParent < 0) {
+      const inlineTextChild = this.inlineTextChild(node);
+      if (inlineTextChild) {
+        acc = accService.getAccessibleFor(inlineTextChild.rawNode);
+      }
+    }
+
     return acc && acc.indexInParent > -1;
   },
 });
 
 exports.WalkerActor = WalkerActor;