Commit e998d303 authored by James Teh's avatar James Teh
Browse files

Bug 1773930: Expose xml-roles object attribute on cached RemoteAccessible. r=morgan

This also changes LocalAccessible::Attributes to ignore the edge case of an empty role attribute when calculating xml-roles.
This is consistent with how we handle the role attribute elsewhere (see aria::GetRoleMapIndex) and makes it easier to handle this consistently between local and remote.

Differential Revision: https://phabricator.services.mozilla.com/D149594
parent cbdc83f7
Loading
Loading
Loading
Loading
+19 −3
Original line number Diff line number Diff line
@@ -1088,7 +1088,8 @@ already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
  // 'xml-roles' attribute coming from ARIA.
  nsString xmlRoles;
  if (mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::role,
                                     xmlRoles)) {
                                     xmlRoles) &&
      !xmlRoles.IsEmpty()) {
    attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(xmlRoles));
  } else if (nsAtom* landmark = LandmarkRole()) {
    // 'xml-roles' attribute for landmark.
@@ -3589,17 +3590,32 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
    if (mContent && mContent->IsElement()) {
      fields->SetAttribute(nsGkAtoms::tag, mContent->NodeInfo()->NameAtom());

      dom::Element* el = mContent->AsElement();
      if (IsTextField() || IsDateTimeField()) {
        // Cache text input types. Accessible is recreated if this changes,
        // so it is considered immutable.
        if (const nsAttrValue* attr =
                mContent->AsElement()->GetParsedAttr(nsGkAtoms::type)) {
        if (const nsAttrValue* attr = el->GetParsedAttr(nsGkAtoms::type)) {
          RefPtr<nsAtom> inputType = attr->GetAsAtom();
          if (inputType) {
            fields->SetAttribute(nsGkAtoms::textInputType, inputType);
          }
        }
      }

      // Changing the role attribute currently re-creates the Accessible, so
      // it's immutable in the cache.
      if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
        // Most of the time, the role attribute is a single, known role. We
        // already send the map index, so we don't need to double up.
        if (!el->AttrValueIs(kNameSpaceID_None, nsGkAtoms::role,
                             roleMap->roleAtom, eIgnoreCase)) {
          // Multiple roles or unknown roles are rare, so just send them as a
          // string.
          nsAutoString role;
          el->GetAttr(kNameSpaceID_None, nsGkAtoms::role, role);
          fields->SetAttribute(nsGkAtoms::role, std::move(role));
        }
      }
    }

    if (frame) {
+22 −0
Original line number Diff line number Diff line
@@ -821,6 +821,28 @@ already_AddRefed<AccAttributes> RemoteAccessibleBase<Derived>::Attributes() {
    if (auto ariaAttrs = GetCachedARIAAttributes()) {
      ariaAttrs->CopyTo(attributes);
    }

    nsAutoString role;
    mCachedFields->GetAttribute(nsGkAtoms::role, role);
    if (role.IsEmpty()) {
      bool found = false;
      if (const nsRoleMapEntry* roleMap = ARIARoleMap()) {
        if (roleMap->roleAtom != nsGkAtoms::_empty) {
          // Single, known role.
          attributes->SetAttribute(nsGkAtoms::xmlroles, roleMap->roleAtom);
          found = true;
        }
      }
      if (!found) {
        if (nsAtom* landmark = LandmarkRole()) {
          // Landmark role from markup; e.g. HTML <main>.
          attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
        }
      }
    } else {
      // Unknown role or multiple roles.
      attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(role));
    }
  }

  nsAutoString name;
+35 −0
Original line number Diff line number Diff line
@@ -371,3 +371,38 @@ addAccessibleTask(
  },
  { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
);

/**
 * Test support for the xml-roles attribute.
 */
addAccessibleTask(
  `
<div id="knownRole" role="main">knownRole</div>
<div id="emptyRole" role="">emptyRole</div>
<div id="unknownRole" role="foo">unknownRole</div>
<div id="multiRole" role="foo main">multiRole</div>
<main id="markup">markup</main>
<main id="markupWithRole" role="banner">markupWithRole</main>
<main id="markupWithEmptyRole" role="">markupWithEmptyRole</main>
  `,
  async function(browser, docAcc) {
    const knownRole = findAccessibleChildByID(docAcc, "knownRole");
    testAttrs(knownRole, { "xml-roles": "main" }, true);
    const emptyRole = findAccessibleChildByID(docAcc, "emptyRole");
    testAbsentAttrs(emptyRole, { "xml-roles": "" });
    const unknownRole = findAccessibleChildByID(docAcc, "unknownRole");
    testAttrs(unknownRole, { "xml-roles": "foo" }, true);
    const multiRole = findAccessibleChildByID(docAcc, "multiRole");
    testAttrs(multiRole, { "xml-roles": "foo main" }, true);
    const markup = findAccessibleChildByID(docAcc, "markup");
    testAttrs(markup, { "xml-roles": "main" }, true);
    const markupWithRole = findAccessibleChildByID(docAcc, "markupWithRole");
    testAttrs(markupWithRole, { "xml-roles": "banner" }, true);
    const markupWithEmptyRole = findAccessibleChildByID(
      docAcc,
      "markupWithEmptyRole"
    );
    testAttrs(markupWithEmptyRole, { "xml-roles": "main" }, true);
  },
  { chrome: true, topLevel: true, iframe: true, remoteIframe: true }
);