Bug 1620324 - Part 1: Add mac accessible intermediate root group when needed. r=morgan
☠☠ backed out by 42f436b26f0a ☠ ☠
authorEitan Isaacson <eitan@monotonous.org>
Wed, 16 Sep 2020 20:20:00 +0000
changeset 549006 2ee894a67a931b9a594691081f33156897c89b04
parent 549005 cbfdd5245340bc240bab5ccb3a81878ca5143c2d
child 549007 ea2f00c4049e3bb28bf0a57edba3eeed15a5445e
push id126522
push usereisaacson@mozilla.com
push dateWed, 16 Sep 2020 20:24:07 +0000
treeherderautoland@ea2f00c4049e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmorgan
bugs1620324
milestone82.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 1620324 - Part 1: Add mac accessible intermediate root group when needed. r=morgan When a doc does not have top-level DOM group, or consists of a single leaf, we need to insert a generated root group. The rotor API expects this and uses it for boundary detection. Differential Revision: https://phabricator.services.mozilla.com/D90174
accessible/mac/MOXAccessibleProtocol.h
accessible/mac/MOXWebAreaAccessible.h
accessible/mac/MOXWebAreaAccessible.mm
accessible/mac/mozAccessible.mm
accessible/tests/browser/mac/browser.ini
accessible/tests/browser/mac/browser_rootgroup.js
accessible/tests/browser/mac/browser_whitespace.js
--- a/accessible/mac/MOXAccessibleProtocol.h
+++ b/accessible/mac/MOXAccessibleProtocol.h
@@ -200,16 +200,19 @@
 - (NSValue* _Nullable)moxColumnIndexRange;
 
 // AXRowHeaderUIElements
 - (NSArray* _Nullable)moxRowHeaderUIElements;
 
 // AXColumnHeaderUIElements
 - (NSArray* _Nullable)moxColumnHeaderUIElements;
 
+// AXIdentifier
+- (NSString* _Nullable)moxIdentifier;
+
 // Math Attributes
 
 // AXMathRootRadicand
 - (id _Nullable)moxMathRootRadicand;
 
 // AXMathRootIndex
 - (id _Nullable)moxMathRootIndex;
 
--- a/accessible/mac/MOXWebAreaAccessible.h
+++ b/accessible/mac/MOXWebAreaAccessible.h
@@ -5,35 +5,49 @@
  * 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/. */
 
 #import "mozAccessible.h"
 #include "Pivot.h"
 
 using namespace mozilla::a11y;
 
-@interface MOXWebAreaAccessible : mozAccessible
+@class MOXRootGroup;
+
+@interface MOXWebAreaAccessible : mozAccessible {
+  MOXRootGroup* mRootGroup;
+}
 // overrides
 - (NSURL*)moxURL;
 
 // overrides
 - (NSNumber*)moxLoaded;
 
 // overrides
 - (NSNumber*)moxLoadingProgress;
 
 // override
 - (NSArray*)moxUIElementsForSearchPredicate:(NSDictionary*)searchPredicate;
 
 // override
 - (NSNumber*)moxUIElementCountForSearchPredicate:(NSDictionary*)searchPredicate;
 
-// overrides
+// override
+- (NSArray*)moxUnignoredChildren;
+
+// override
 - (void)handleAccessibleEvent:(uint32_t)eventType;
 
+// override
+- (void)dealloc;
+
+- (NSArray*)rootGroupChildren;
+
+- (id)rootGroup;
+
 @end
 
 @interface MOXSearchInfo : NSObject {
   // The gecko accessible of the web area, we need a reference
   // to set the pivot's root. This is a weak ref.
   AccessibleOrProxy mWebArea;
 
   // The gecko accessible we should start searching from.
--- a/accessible/mac/MOXWebAreaAccessible.mm
+++ b/accessible/mac/MOXWebAreaAccessible.mm
@@ -8,16 +8,118 @@
 #import "MOXWebAreaAccessible.h"
 #import "RotorRules.h"
 
 #include "nsCocoaUtils.h"
 #include "DocAccessibleParent.h"
 
 using namespace mozilla::a11y;
 
+@interface MOXRootGroup : MOXAccessibleBase {
+  MOXWebAreaAccessible* mParent;
+}
+
+// override
+- (id)initWithParent:(MOXWebAreaAccessible*)parent;
+
+// override
+- (NSString*)moxRole;
+
+// override
+- (NSString*)moxRoleDescription;
+
+// override
+- (id<mozAccessible>)moxParent;
+
+// override
+- (NSArray*)moxChildren;
+
+// override
+- (NSString*)moxIdentifier;
+
+// override
+- (id)moxHitTest:(NSPoint)point;
+
+// override
+- (NSValue*)moxPosition;
+
+// override
+- (NSValue*)moxSize;
+
+// override
+- (BOOL)disableChild:(id)child;
+
+// override
+- (void)expire;
+
+// override
+- (BOOL)isExpired;
+
+@end
+
+@implementation MOXRootGroup
+
+- (id)initWithParent:(MOXWebAreaAccessible*)parent {
+  // The parent is always a MOXWebAreaAccessible
+  mParent = parent;
+  return [super init];
+}
+
+- (NSString*)moxRole {
+  return NSAccessibilityGroupRole;
+}
+
+- (NSString*)moxRoleDescription {
+  return NSAccessibilityRoleDescription(NSAccessibilityGroupRole, nil);
+}
+
+- (id<mozAccessible>)moxParent {
+  return mParent;
+}
+
+- (NSArray*)moxChildren {
+  // Reparent the children of the web area here.
+  return [mParent rootGroupChildren];
+}
+
+- (NSString*)moxIdentifier {
+  // This is mostly for testing purposes to assert that this is the generated
+  // root group.
+  return @"root-group";
+}
+
+- (id)moxHitTest:(NSPoint)point {
+  return [mParent moxHitTest:point];
+}
+
+- (NSValue*)moxPosition {
+  return [mParent moxPosition];
+}
+
+- (NSValue*)moxSize {
+  return [mParent moxSize];
+}
+
+- (BOOL)disableChild:(id)child {
+  return NO;
+}
+
+- (void)expire {
+  mParent = nil;
+  [super expire];
+}
+
+- (BOOL)isExpired {
+  MOZ_ASSERT((mParent == nil) == mIsExpired);
+
+  return [super isExpired];
+}
+
+@end
+
 @implementation MOXWebAreaAccessible
 
 - (NSURL*)moxURL {
   if ([self isExpired]) {
     return nil;
   }
 
   nsAutoString url;
@@ -107,16 +209,60 @@ using namespace mozilla::a11y;
         [self moxPostNotification:@"AXLayoutComplete"];
       }
       break;
   }
 
   [super handleAccessibleEvent:eventType];
 }
 
+- (NSArray*)rootGroupChildren {
+  // This method is meant to expose the doc's children to the root group.
+  return [super moxChildren];
+}
+
+- (NSArray*)moxUnignoredChildren {
+  if (id rootGroup = [self rootGroup]) {
+    return @[ [self rootGroup] ];
+  }
+
+  // There is no root group, expose the children here directly.
+  return [super moxUnignoredChildren];
+}
+
+- (id)rootGroup {
+  NSArray* children = [super moxUnignoredChildren];
+  if ([children count] == 1 &&
+      [[[children firstObject] moxUnignoredChildren] count] != 0) {
+    // We only need a root group if our document has multiple children or one
+    // child that is a leaf.
+    return nil;
+  }
+
+  if (!mRootGroup) {
+    mRootGroup = [[MOXRootGroup alloc] initWithParent:self];
+  }
+
+  return mRootGroup;
+}
+
+- (void)expire {
+  [mRootGroup expire];
+  [super expire];
+}
+
+- (void)dealloc {
+  // This object can only be dealoced after the gecko accessible wrapper
+  // reference is released, and that happens after expire is called.
+  MOZ_ASSERT([self isExpired]);
+  [mRootGroup release];
+
+  [super dealloc];
+}
+
 @end
 
 @implementation MOXSearchInfo
 
 - (id)initWithParameters:(NSDictionary*)params andRoot:(AccessibleOrProxy)root {
   if (id searchKeyParam = [params objectForKey:@"AXSearchKey"]) {
     mSearchKeys = [searchKeyParam isKindOfClass:[NSString class]]
                       ? @[ searchKeyParam ]
--- a/accessible/mac/mozAccessible.mm
+++ b/accessible/mac/mozAccessible.mm
@@ -290,16 +290,25 @@ static const uint64_t kCacheInitialized 
 
   AccessibleOrProxy parent = mGeckoAccessible.Parent();
 
   if (parent.IsNull()) {
     return nil;
   }
 
   id nativeParent = GetNativeFromGeckoAccessible(parent);
+  if (parent.Role() == roles::DOCUMENT &&
+      [nativeParent respondsToSelector:@selector(rootGroup)]) {
+    // Before returning a WebArea as parent, check to see if
+    // there is a generated root group that is an intermediate container.
+    if (id<mozAccessible> rootGroup = [nativeParent rootGroup]) {
+      nativeParent = rootGroup;
+    }
+  }
+
   if (!nativeParent && mGeckoAccessible.IsAccessible()) {
     // Return native of root accessible if we have no direct parent.
     // XXX: need to return a sensible fallback in proxy case as well
     nativeParent = GetNativeFromGeckoAccessible(
         mGeckoAccessible.AsAccessible()->RootAccessible());
   }
 
   return GetObjectOrRepresentedView(nativeParent);
--- a/accessible/tests/browser/mac/browser.ini
+++ b/accessible/tests/browser/mac/browser.ini
@@ -28,8 +28,9 @@ support-files =
 [browser_focus.js]
 [browser_whitespace.js]
 [browser_webarea.js]
 skip-if = os == 'mac' && !debug #1648813
 [browser_text_basics.js]
 [browser_text_input.js]
 skip-if = os == 'mac' && debug # Bug 1664577
 [browser_rotor.js]
+[browser_rootgroup.js]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/mac/browser_rootgroup.js
@@ -0,0 +1,101 @@
+/* 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";
+
+/**
+ * Test document with no single group child
+ */
+addAccessibleTask(
+  `<p id="p1">hello</p><p>world</p>`,
+  async (browser, accDoc) => {
+    let doc = accDoc.nativeInterface.QueryInterface(
+      Ci.nsIAccessibleMacInterface
+    );
+    let docChildren = doc.getAttributeValue("AXChildren");
+    is(docChildren.length, 1, "The document contains a root group");
+
+    let rootGroup = docChildren[0];
+    is(
+      rootGroup.getAttributeValue("AXIdentifier"),
+      "root-group",
+      "Is generated root group"
+    );
+
+    is(
+      rootGroup.getAttributeValue("AXChildren").length,
+      2,
+      "Root group has two children"
+    );
+
+    // From bottom-up
+    let p1 = getNativeInterface(accDoc, "p1");
+    rootGroup = p1.getAttributeValue("AXParent");
+    is(
+      rootGroup.getAttributeValue("AXIdentifier"),
+      "root-group",
+      "Is generated root group"
+    );
+  }
+);
+
+/**
+ * Test document with a top-level group
+ */
+addAccessibleTask(
+  `<div role="grouping" id="group"><p>hello</p><p>world</p></div>`,
+  async (browser, accDoc) => {
+    let doc = accDoc.nativeInterface.QueryInterface(
+      Ci.nsIAccessibleMacInterface
+    );
+    let docChildren = doc.getAttributeValue("AXChildren");
+    is(docChildren.length, 1, "The document contains a root group");
+
+    let rootGroup = docChildren[0];
+    is(
+      rootGroup.getAttributeValue("AXDOMIdentifier"),
+      "group",
+      "Root group is a document element"
+    );
+  }
+);
+
+/**
+ * Test document with a single button
+ */
+addAccessibleTask(
+  `<button id="button">I am a button</button>`,
+  async (browser, accDoc) => {
+    let doc = accDoc.nativeInterface.QueryInterface(
+      Ci.nsIAccessibleMacInterface
+    );
+    let docChildren = doc.getAttributeValue("AXChildren");
+    is(docChildren.length, 1, "The document contains a root group");
+
+    let rootGroup = docChildren[0];
+    is(
+      rootGroup.getAttributeValue("AXIdentifier"),
+      "root-group",
+      "Is generated root group"
+    );
+
+    let rootGroupChildren = rootGroup.getAttributeValue("AXChildren");
+    is(rootGroupChildren.length, 1, "Root group has one children");
+
+    is(
+      rootGroupChildren[0].getAttributeValue("AXRole"),
+      "AXButton",
+      "Button is child of root group"
+    );
+
+    // From bottom-up
+    let button = getNativeInterface(accDoc, "button");
+    rootGroup = button.getAttributeValue("AXParent");
+    is(
+      rootGroup.getAttributeValue("AXIdentifier"),
+      "root-group",
+      "Is generated root group"
+    );
+  }
+);
--- a/accessible/tests/browser/mac/browser_whitespace.js
+++ b/accessible/tests/browser/mac/browser_whitespace.js
@@ -8,36 +8,40 @@
 loadScripts({ name: "role.js", dir: MOCHITESTS_DIR });
 
 /**
  * Test accessibles aren't created for linebreaks.
  */
 addAccessibleTask(`hello<br>world`, async (browser, accDoc) => {
   let doc = accDoc.nativeInterface.QueryInterface(Ci.nsIAccessibleMacInterface);
   let docChildren = doc.getAttributeValue("AXChildren");
-  is(docChildren.length, 2, "The document contains two children");
+  is(docChildren.length, 1, "The document contains a root group");
+
+  let rootGroup = docChildren[0];
+  let children = rootGroup.getAttributeValue("AXChildren");
+  is(docChildren.length, 1, "The root group contains 2 children");
 
   // verify first child is correct
   is(
-    docChildren[0].getAttributeValue("AXRole"),
+    children[0].getAttributeValue("AXRole"),
     "AXStaticText",
     "First child is a text node"
   );
   is(
-    docChildren[0].getAttributeValue("AXValue"),
+    children[0].getAttributeValue("AXValue"),
     "hello",
     "First child is hello text"
   );
 
   // verify second child is correct
   is(
-    docChildren[1].getAttributeValue("AXRole"),
+    children[1].getAttributeValue("AXRole"),
     "AXStaticText",
     "Second child is a text node"
   );
 
   is(
-    docChildren[1].getAttributeValue("AXValue"),
+    children[1].getAttributeValue("AXValue"),
     "world ",
     "Second child is world text"
   );
   // we have a trailing space here due to bug 1577028
 });