Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  LocalAccessible.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */


#include "AccEvent.h"
#include "LocalAccessible-inl.h"

#include "EmbeddedObjCollector.h"
#include "AccGroupInfo.h"
#include "AccIterator.h"
#include "CachedTableAccessible.h"
#include "CssAltContent.h"
#include "DocAccessible-inl.h"
#include "mozilla/a11y/AccAttributes.h"
#include "mozilla/a11y/DocAccessibleChild.h"
#include "mozilla/a11y/Platform.h"
#include "mozilla/FocusModel.h"
#include "nsAccUtils.h"
#include "nsAccessibilityService.h"
#include "ApplicationAccessible.h"
#include "nsGenericHTMLElement.h"
#include "NotificationController.h"
#include "nsEventShell.h"
#include "nsTextEquivUtils.h"
#include "EventTree.h"
#include "OuterDocAccessible.h"
#include "Pivot.h"
#include "Relation.h"
#include "mozilla/a11y/Role.h"
#include "RootAccessible.h"
#include "States.h"
#include "TextLeafRange.h"
#include "TextRange.h"
#include "HTMLElementAccessibles.h"
#include "HTMLSelectAccessible.h"
#include "HTMLTableAccessible.h"
#include "ImageAccessible.h"

#include "nsComputedDOMStyle.h"
#include "nsGkAtoms.h"
#include "nsIDOMXULButtonElement.h"
#include "nsIDOMXULSelectCntrlEl.h"
#include "nsIDOMXULSelectCntrlItemEl.h"
#include "nsINodeList.h"

#include "mozilla/dom/Document.h"
#include "mozilla/dom/HTMLFormElement.h"
#include "mozilla/dom/HTMLAnchorElement.h"
#include "mozilla/gfx/Matrix.h"
#include "nsIContent.h"
#include "nsIFormControl.h"

#include "nsDisplayList.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsIFrame.h"
#include "nsTextFrame.h"
#include "nsView.h"
#include "nsIDocShellTreeItem.h"
#include "nsStyleStructInlines.h"
#include "nsFocusManager.h"

#include "nsString.h"
#include "nsAtom.h"
#include "nsContainerFrame.h"

#include "mozilla/Assertions.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/ScrollContainerFrame.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLLabelElement.h"
#include "mozilla/dom/KeyboardEventBinding.h"
#include "mozilla/dom/TreeWalker.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/MutationEventBinding.h"

using namespace mozilla;
using namespace mozilla::a11y;

////////////////////////////////////////////////////////////////////////////////
// LocalAccessible: nsISupports and cycle collection

NS_IMPL_CYCLE_COLLECTION_CLASS(LocalAccessible)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(LocalAccessible)
  tmp->Shutdown();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(LocalAccessible)
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent, mDoc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LocalAccessible)
  NS_INTERFACE_MAP_ENTRY_CONCRETE(LocalAccessible)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, LocalAccessible)
NS_INTERFACE_MAP_END

NS_IMPL_CYCLE_COLLECTING_ADDREF(LocalAccessible)
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(LocalAccessible, LastRelease())

LocalAccessible::LocalAccessible(nsIContent* aContent, DocAccessible* aDoc)
    : mContent(aContent),
      mDoc(aDoc),
      mParent(nullptr),
      mIndexInParent(-1),
      mFirstLineStart(-1),
      mStateFlags(0),
      mContextFlags(0),
      mReorderEventTarget(false),
      mShowEventTarget(false),
      mHideEventTarget(false),
      mIndexOfEmbeddedChild(-1),
      mGroupInfo(nullptr) {}

LocalAccessible::~LocalAccessible() {
  NS_ASSERTION(!mDoc, "LastRelease was never called!?!");
}

ENameValueFlag LocalAccessible::Name(nsString& aName) const {
  aName.Truncate();

  if (!HasOwnContent()) return eNameOK;

  ARIAName(aName);
  if (!aName.IsEmpty()) return eNameOK;

  ENameValueFlag nameFlag = NativeName(aName);
  if (!aName.IsEmpty()) return nameFlag;

  // In the end get the name from tooltip.
  if (mContent->IsHTMLElement()) {
    if (mContent->AsElement()->GetAttr(nsGkAtoms::title, aName)) {
      aName.CompressWhitespace();
      return eNameFromTooltip;
    }
  } else if (mContent->IsXULElement()) {
    if (mContent->AsElement()->GetAttr(nsGkAtoms::tooltiptext, aName)) {
      aName.CompressWhitespace();
      return eNameFromTooltip;
    }
  } else if (mContent->IsSVGElement()) {
    // If user agents need to choose among multiple 'desc' or 'title'
    // elements for processing, the user agent shall choose the first one.
    for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
         childElm = childElm->GetNextSibling()) {
      if (childElm->IsSVGElement(nsGkAtoms::desc)) {
        nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
        return eNameFromTooltip;
      }
    }
  }

  if (auto cssAlt = CssAltContent(mContent)) {
    cssAlt.AppendToString(aName);
    return eNameOK;
  }

  aName.SetIsVoid(true);

  return nameFlag;
}

void LocalAccessible::Description(nsString& aDescription) const {
  // There are 4 conditions that make an accessible have no accDescription:
  // 1. it's a text node; or
  // 2. It has no ARIA describedby or description property
  // 3. it doesn't have an accName; or
  // 4. its title attribute already equals to its accName nsAutoString name;

  if (!HasOwnContent() || mContent->IsText()) return;

  ARIADescription(aDescription);

  if (aDescription.IsEmpty()) {
    NativeDescription(aDescription);

    if (aDescription.IsEmpty()) {
      // Keep the Name() method logic.
      if (mContent->IsHTMLElement()) {
        mContent->AsElement()->GetAttr(nsGkAtoms::title, aDescription);
      } else if (mContent->IsXULElement()) {
        mContent->AsElement()->GetAttr(nsGkAtoms::tooltiptext, aDescription);
      } else if (mContent->IsSVGElement()) {
        for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
             childElm = childElm->GetNextSibling()) {
          if (childElm->IsSVGElement(nsGkAtoms::desc)) {
            nsTextEquivUtils::AppendTextEquivFromContent(this, childElm,
                                                         &aDescription);
            break;
          }
        }
      }
    }
  }

  if (!aDescription.IsEmpty()) {
    aDescription.CompressWhitespace();
    nsAutoString name;
    Name(name);
    // Don't expose a description if it is the same as the name.
    if (aDescription.Equals(name)) aDescription.Truncate();
  }
}

KeyBinding LocalAccessible::AccessKey() const {
  if (!HasOwnContent()) return KeyBinding();

  uint32_t key = nsCoreUtils::GetAccessKeyFor(mContent);
  if (!key && mContent->IsElement()) {
    LocalAccessible* label = nullptr;

    // Copy access key from label node.
    if (mContent->IsHTMLElement()) {
      // Unless it is labeled via an ancestor <label>, in which case that would
      // be redundant.
      HTMLLabelIterator iter(Document(), this,
                             HTMLLabelIterator::eSkipAncestorLabel);
      label = iter.Next();
    }
    if (!label) {
      XULLabelIterator iter(Document(), mContent);
      label = iter.Next();
    }

    if (label) key = nsCoreUtils::GetAccessKeyFor(label->GetContent());
  }

  if (!key) return KeyBinding();

  // Get modifier mask. Use ui.key.generalAccessKey (unless it is -1).
  switch (StaticPrefs::ui_key_generalAccessKey()) {
    case -1:
      break;
    case dom::KeyboardEvent_Binding::DOM_VK_SHIFT:
      return KeyBinding(key, KeyBinding::kShift);
    case dom::KeyboardEvent_Binding::DOM_VK_CONTROL:
      return KeyBinding(key, KeyBinding::kControl);
    case dom::KeyboardEvent_Binding::DOM_VK_ALT:
      return KeyBinding(key, KeyBinding::kAlt);
    case dom::KeyboardEvent_Binding::DOM_VK_META:
      return KeyBinding(key, KeyBinding::kMeta);
    default:
      return KeyBinding();
  }

  // Determine the access modifier used in this context.
  dom::Document* document = mContent->GetComposedDoc();
  if (!document) return KeyBinding();

  nsCOMPtr<nsIDocShellTreeItem> treeItem(document->GetDocShell());
  if (!treeItem) return KeyBinding();

  nsresult rv = NS_ERROR_FAILURE;
  int32_t modifierMask = 0;
  switch (treeItem->ItemType()) {
    case nsIDocShellTreeItem::typeChrome:
      modifierMask = StaticPrefs::ui_key_chromeAccess();
      rv = NS_OK;
      break;
    case nsIDocShellTreeItem::typeContent:
      modifierMask = StaticPrefs::ui_key_contentAccess();
      rv = NS_OK;
      break;
  }

  return NS_SUCCEEDED(rv) ? KeyBinding(key, modifierMask) : KeyBinding();
}

KeyBinding LocalAccessible::KeyboardShortcut() const { return KeyBinding(); }

uint64_t LocalAccessible::VisibilityState() const {
  if (IPCAccessibilityActive()) {
    // Visibility states must be calculated by RemoteAccessible, so there's no
    // point calculating them here.
    return 0;
  }
  nsIFrame* frame = GetFrame();
  if (!frame) {
    // Element having display:contents is considered visible semantically,
    // despite it doesn't have a visually visible box.
    if (nsCoreUtils::IsDisplayContents(mContent)) {
      return states::OFFSCREEN;
    }
    return states::INVISIBLE;
  }

  if (!frame->StyleVisibility()->IsVisible()) return states::INVISIBLE;

  // It's invisible if the presshell is hidden by a visibility:hidden element in
  // an ancestor document.
  if (frame->PresShell()->IsUnderHiddenEmbedderElement()) {
    return states::INVISIBLE;
  }

  // Offscreen state if the document's visibility state is not visible.
  if (Document()->IsHidden()) return states::OFFSCREEN;

  // Walk the parent frame chain to see if the frame is in background tab or
  // scrolled out.
  nsIFrame* curFrame = frame;
  do {
    nsView* view = curFrame->GetView();
    if (view && view->GetVisibility() == ViewVisibility::Hide) {
      return states::INVISIBLE;
    }

    if (nsLayoutUtils::IsPopup(curFrame)) {
      return 0;
    }

    if (curFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
      // Offscreen state for background tab content.
      return states::OFFSCREEN;
    }

    nsIFrame* parentFrame = curFrame->GetParent();
    // If contained by scrollable frame then check that at least 12 pixels
    // around the object is visible, otherwise the object is offscreen.
    const nscoord kMinPixels = nsPresContext::CSSPixelsToAppUnits(12);
    if (ScrollContainerFrame* scrollContainerFrame =
            do_QueryFrame(parentFrame)) {
      nsRect scrollPortRect = scrollContainerFrame->GetScrollPortRect();
      nsRect frameRect = nsLayoutUtils::TransformFrameRectToAncestor(
          frame, frame->GetRectRelativeToSelf(), parentFrame);
      if (!scrollPortRect.Contains(frameRect)) {
        scrollPortRect.Deflate(kMinPixels, kMinPixels);
        if (!scrollPortRect.Intersects(frameRect)) return states::OFFSCREEN;
      }
    }

    if (!parentFrame) {
      parentFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(curFrame);
      // Even if we couldn't find the parent frame, it might mean we are in an
      // out-of-process iframe, try to see if |frame| is scrolled out in an
      // scrollable frame in a cross-process ancestor document.
      if (!parentFrame &&
          nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess(
              frame, kMinPixels)) {
        return states::OFFSCREEN;
      }
    }

    curFrame = parentFrame;
  } while (curFrame);

  // Zero area rects can occur in the first frame of a multi-frame text flow,
  // in which case the rendered text is not empty and the frame should not be
  // marked invisible.
  // XXX Can we just remove this check? Why do we need to mark empty
  // text invisible?
  if (frame->IsTextFrame() && !frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
      frame->GetRect().IsEmpty()) {
    nsIFrame::RenderedText text = frame->GetRenderedText(
        0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
        nsIFrame::TrailingWhitespace::DontTrim);
    if (text.mString.IsEmpty()) {
      return states::INVISIBLE;
    }
  }

  return 0;
}

uint64_t LocalAccessible::NativeState() const {
  uint64_t state = 0;

  if (!IsInDocument()) state |= states::STALE;

  if (HasOwnContent() && mContent->IsElement()) {
    dom::ElementState elementState = mContent->AsElement()->State();

    if (elementState.HasState(dom::ElementState::INVALID)) {
      state |= states::INVALID;
    }

    if (elementState.HasState(dom::ElementState::REQUIRED)) {
      state |= states::REQUIRED;
    }

    state |= NativeInteractiveState();
  }

  // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
  state |= VisibilityState();

  nsIFrame* frame = GetFrame();
  if (frame && frame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
    state |= states::FLOATING;
  }

  // Check if a XUL element has the popup attribute (an attached popup menu).
  if (HasOwnContent() && mContent->IsXULElement() &&
      mContent->AsElement()->HasAttr(nsGkAtoms::popup)) {
    state |= states::HASPOPUP;
  }

  // Bypass the link states specialization for non links.
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
  if (!roleMapEntry || roleMapEntry->roleRule == kUseNativeRole ||
      roleMapEntry->role == roles::LINK) {
    state |= NativeLinkState();
  }

  return state;
}

uint64_t LocalAccessible::NativeInteractiveState() const {
  if (!mContent->IsElement()) return 0;

  if (NativelyUnavailable()) return states::UNAVAILABLE;

  nsIFrame* frame = GetFrame();
  auto flags = IsFocusableFlags(0);
  // If we're caching this remote document in the parent process, we
  // need to cache focusability irrespective of visibility. Otherwise,
  // if this document is invisible when it first loads, we'll cache that
  // all descendants are unfocusable and this won't get updated when the
  // document becomes visible. Even if we did get notified when the
  // document becomes visible, it would be wasteful to walk the entire
  // tree to figure out what is now focusable and push cache updates.
  // Although ignoring visibility means IsFocusable will return true for
  // visibility: hidden, etc., this isn't a problem because we don't include
  // those hidden elements in the a11y tree anyway.
  if (mDoc->IPCDoc()) {
    flags |= IsFocusableFlags::IgnoreVisibility;
  }
  if (frame && frame->IsFocusable(flags)) {
    return states::FOCUSABLE;
  }
  return 0;
}

uint64_t LocalAccessible::NativeLinkState() const { return 0; }

bool LocalAccessible::NativelyUnavailable() const {
  if (mContent->IsHTMLElement()) return mContent->AsElement()->IsDisabled();

  return mContent->IsElement() && mContent->AsElement()->AttrValueIs(
                                      kNameSpaceID_None, nsGkAtoms::disabled,
                                      nsGkAtoms::_true, eCaseMatters);
}

Accessible* LocalAccessible::ChildAtPoint(int32_t aX, int32_t aY,
                                          EWhichChildAtPoint aWhichChild) {
  Accessible* child = LocalChildAtPoint(aX, aY, aWhichChild);
  if (aWhichChild != EWhichChildAtPoint::DirectChild && child &&
      child->IsOuterDoc()) {
    child = child->ChildAtPoint(aX, aY, aWhichChild);
  }

  return child;
}

LocalAccessible* LocalAccessible::LocalChildAtPoint(
    int32_t aX, int32_t aY, EWhichChildAtPoint aWhichChild) {
  // If we can't find the point in a child, we will return the fallback answer:
  // we return |this| if the point is within it, otherwise nullptr.
  LocalAccessible* fallbackAnswer = nullptr;
  LayoutDeviceIntRect rect = Bounds();
  if (rect.Contains(aX, aY)) fallbackAnswer = this;

  if (nsAccUtils::MustPrune(this)) {  // Do not dig any further
    return fallbackAnswer;
  }

  // Search an accessible at the given point starting from accessible document
  // because containing block (see CSS2) for out of flow element (for example,
  // absolutely positioned element) may be different from its DOM parent and
  // therefore accessible for containing block may be different from accessible
  // for DOM parent but GetFrameForPoint() should be called for containing block
  // to get an out of flow element.
  DocAccessible* accDocument = Document();
  NS_ENSURE_TRUE(accDocument, nullptr);

  nsIFrame* rootFrame = accDocument->GetFrame();
  NS_ENSURE_TRUE(rootFrame, nullptr);

  nsIFrame* startFrame = rootFrame;

  // Check whether the point is at popup content.
  nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr);
  NS_ENSURE_TRUE(rootWidget, nullptr);

  LayoutDeviceIntRect rootRect = rootWidget->GetScreenBounds();

  auto point = LayoutDeviceIntPoint(aX - rootRect.X(), aY - rootRect.Y());

  nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
      accDocument->PresContext()->GetRootPresContext(), rootWidget, point);
  if (popupFrame) {
    // If 'this' accessible is not inside the popup then ignore the popup when
    // searching an accessible at point.
    DocAccessible* popupDoc =
        GetAccService()->GetDocAccessible(popupFrame->GetContent()->OwnerDoc());
    LocalAccessible* popupAcc =
        popupDoc->GetAccessibleOrContainer(popupFrame->GetContent());
    LocalAccessible* popupChild = this;
    while (popupChild && !popupChild->IsDoc() && popupChild != popupAcc) {
      popupChild = popupChild->LocalParent();
    }

    if (popupChild == popupAcc) startFrame = popupFrame;
  }

  nsPresContext* presContext = startFrame->PresContext();
  nsRect screenRect = startFrame->GetScreenRectInAppUnits();
  nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.X(),
                 presContext->DevPixelsToAppUnits(aY) - screenRect.Y());

  nsIFrame* foundFrame = nsLayoutUtils::GetFrameForPoint(
      RelativeTo{startFrame, ViewportType::Visual}, offset);

  nsIContent* content = nullptr;
  if (!foundFrame || !(content = foundFrame->GetContent())) {
    return fallbackAnswer;
  }

  // Get accessible for the node with the point or the first accessible in
  // the DOM parent chain.
  DocAccessible* contentDocAcc =
      GetAccService()->GetDocAccessible(content->OwnerDoc());

  // contentDocAcc in some circumstances can be nullptr. See bug 729861
  NS_ASSERTION(contentDocAcc, "could not get the document accessible");
  if (!contentDocAcc) return fallbackAnswer;

  LocalAccessible* accessible =
      contentDocAcc->GetAccessibleOrContainer(content);
  if (!accessible) return fallbackAnswer;

  // Hurray! We have an accessible for the frame that layout gave us.
  // Since DOM node of obtained accessible may be out of flow then we should
  // ensure obtained accessible is a child of this accessible.
  LocalAccessible* child = accessible;
  while (child != this) {
    LocalAccessible* parent = child->LocalParent();
    if (!parent) {
      // Reached the top of the hierarchy. These bounds were inside an
      // accessible that is not a descendant of this one.
      return fallbackAnswer;
    }

    // If we landed on a legitimate child of |this|, and we want the direct
    // child, return it here.
    if (parent == this && aWhichChild == EWhichChildAtPoint::DirectChild) {
      return child;
    }

    child = parent;
  }

  // Manually walk through accessible children and see if the are within this
  // point. Skip offscreen or invisible accessibles. This takes care of cases
  // where layout won't walk into things for us, such as image map areas and
  // sub documents (XXX: subdocuments should be handled by methods of
  // OuterDocAccessibles).
  uint32_t childCount = accessible->ChildCount();
  if (childCount == 1 && accessible->IsOuterDoc() &&
      accessible->FirstChild()->IsRemote()) {
    // No local children.
    return accessible;
  }
  for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
    LocalAccessible* child = accessible->LocalChildAt(childIdx);

    LayoutDeviceIntRect childRect = child->Bounds();
    if (childRect.Contains(aX, aY) &&
        (child->State() & states::INVISIBLE) == 0) {
      if (aWhichChild == EWhichChildAtPoint::DeepestChild) {
        return child->LocalChildAtPoint(aX, aY,
                                        EWhichChildAtPoint::DeepestChild);
      }

      return child;
    }
  }

  return accessible;
}

nsIFrame* LocalAccessible::FindNearestAccessibleAncestorFrame() {
  nsIFrame* frame = GetFrame();
  if (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
      nsLayoutUtils::IsReallyFixedPos(frame)) {
    return mDoc->PresShellPtr()->GetRootFrame();
  }

  if (IsDoc()) {
    // We bound documents by their own frame, which is their PresShell's root
    // frame. We cache the document offset elsewhere in BundleFieldsForCache
    // using the nsGkAtoms::crossorigin attribute.
    MOZ_ASSERT(frame, "DocAccessibles should always have a frame");
    return frame;
  }

  // Iterate through accessible's ancestors to find one with a frame.
  LocalAccessible* ancestor = mParent;
  while (ancestor) {
    if (nsIFrame* boundingFrame = ancestor->GetFrame()) {
      return boundingFrame;
    }
    ancestor = ancestor->LocalParent();
  }

  MOZ_ASSERT_UNREACHABLE("No ancestor with frame?");
  return nsLayoutUtils::GetContainingBlockForClientRect(frame);
}

nsRect LocalAccessible::ParentRelativeBounds() {
  nsIFrame* frame = GetFrame();
  if (frame && mContent) {
    nsIFrame* boundingFrame = FindNearestAccessibleAncestorFrame();
    nsRect result = nsLayoutUtils::GetAllInFlowRectsUnion(frame, boundingFrame);

    if (result.IsEmpty()) {
      // If we end up with a 0x0 rect from above (or one with negative
      // height/width) we should try using the ink overflow rect instead. If we
      // use this rect, our relative bounds will match the bounds of what
      // appears visually. We do this because some web authors (icloud.com for
      // example) employ things like 0x0 buttons with visual overflow. Without
      // this, such frames aren't navigable by screen readers.
      result = frame->InkOverflowRectRelativeToSelf();
      result.MoveBy(frame->GetOffsetTo(boundingFrame));
    }

    if (boundingFrame->GetRect().IsEmpty() ||
        nsLayoutUtils::GetNextContinuationOrIBSplitSibling(boundingFrame)) {
      // Constructing a bounding box across a frame that has an IB split means
      // the origin is likely be different from that of boundingFrame.
      // Descendants will need their parent-relative bounds adjusted
      // accordingly, since parent-relative bounds are constructed to the
      // bounding box of the entire element and not each individual IB split
      // frame. In the case that boundingFrame's rect is empty,
      // GetAllInFlowRectsUnion might exclude its origin. For example, if
      // boundingFrame is empty with an origin of (0, -840) but has a non-empty
      // ib-split-sibling with (0, 0), the union rect will originate at (0, 0).
      // This means the bounds returned for our parent Accessible might be
      // offset from boundingFrame's rect. Since result is currently relative to
      // boundingFrame's rect, we might need to adjust it to make it parent
      // relative.
      nsRect boundingUnion =
          nsLayoutUtils::GetAllInFlowRectsUnion(boundingFrame, boundingFrame);
      if (!boundingUnion.IsEmpty()) {
        // The origin of boundingUnion is relative to boundingFrame, meaning
        // when we call MoveBy on result with this value we're offsetting
        // `result` by the distance boundingFrame's origin was moved to
        // construct its bounding box.
        result.MoveBy(-boundingUnion.TopLeft());
      } else {
        // Since GetAllInFlowRectsUnion returned an empty rect on our parent
        // Accessible, we would have used the ink overflow rect. However,
        // GetAllInFlowRectsUnion calculates relative to the bounding frame's
        // main rect, not its ink overflow rect. We need to adjust for the ink
        // overflow offset to make our result parent relative.
        nsRect boundingOverflow =
            boundingFrame->InkOverflowRectRelativeToSelf();
        result.MoveBy(-boundingOverflow.TopLeft());
      }
    }

    if (frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
        nsLayoutUtils::IsReallyFixedPos(frame)) {
      // If we're dealing with a fixed position frame, we've already made it
      // relative to the document which should have gotten rid of its scroll
      // offset.
      return result;
    }

    if (ScrollContainerFrame* sf =
            mParent == mDoc
                ? mDoc->PresShellPtr()->GetRootScrollContainerFrame()
                : boundingFrame->GetScrollTargetFrame()) {
      // If boundingFrame has a scroll position, result is currently relative
      // to that. Instead, we want result to remain the same regardless of
      // scrolling. We then subtract the scroll position later when
      // calculating absolute bounds. We do this because we don't want to push
      // cache updates for the bounds of all descendants every time we scroll.
      nsPoint scrollPos = sf->GetScrollPosition().ApplyResolution(
          mDoc->PresShellPtr()->GetResolution());
      result.MoveBy(scrollPos.x, scrollPos.y);
    }

    return result;
  }

  return nsRect();
}

nsRect LocalAccessible::RelativeBounds(nsIFrame** aBoundingFrame) const {
  nsIFrame* frame = GetFrame();
  if (frame && mContent) {
    *aBoundingFrame = nsLayoutUtils::GetContainingBlockForClientRect(frame);
    nsRect unionRect = nsLayoutUtils::GetAllInFlowRectsUnion(
        frame, *aBoundingFrame,
        nsLayoutUtils::GetAllInFlowRectsFlag::AccountForTransforms);

    if (unionRect.IsEmpty()) {
      // If we end up with a 0x0 rect from above (or one with negative
      // height/width) we should try using the ink overflow rect instead. If we
      // use this rect, our relative bounds will match the bounds of what
      // appears visually. We do this because some web authors (icloud.com for
      // example) employ things like 0x0 buttons with visual overflow. Without
      // this, such frames aren't navigable by screen readers.
      nsRect overflow = frame->InkOverflowRectRelativeToSelf();
      nsLayoutUtils::TransformRect(frame, *aBoundingFrame, overflow);
      return overflow;
    }

    return unionRect;
  }

  return nsRect();
}

nsRect LocalAccessible::BoundsInAppUnits() const {
  nsIFrame* boundingFrame = nullptr;
  nsRect unionRectTwips = RelativeBounds(&boundingFrame);
  if (!boundingFrame) {
    return nsRect();
  }

  PresShell* presShell = mDoc->PresContext()->PresShell();

  // We need to inverse translate with the offset of the edge of the visual
  // viewport from top edge of the layout viewport.
  nsPoint viewportOffset = presShell->GetVisualViewportOffset() -
                           presShell->GetLayoutViewportOffset();
  unionRectTwips.MoveBy(-viewportOffset);

  // We need to take into account a non-1 resolution set on the presshell.
  // This happens with async pinch zooming. Here we scale the bounds before
  // adding the screen-relative offset.
  unionRectTwips.ScaleRoundOut(presShell->GetResolution());
  // We have the union of the rectangle, now we need to put it in absolute
  // screen coords.
  nsRect orgRectPixels = boundingFrame->GetScreenRectInAppUnits();
  unionRectTwips.MoveBy(orgRectPixels.X(), orgRectPixels.Y());

  return unionRectTwips;
}

LayoutDeviceIntRect LocalAccessible::Bounds() const {
  return LayoutDeviceIntRect::FromAppUnitsToNearest(
      BoundsInAppUnits(), mDoc->PresContext()->AppUnitsPerDevPixel());
}

void LocalAccessible::SetSelected(bool aSelect) {
  if (!HasOwnContent()) return;

  if (nsAccUtils::GetSelectableContainer(this, State()) && aSelect) {
    TakeFocus();
  }
}

void LocalAccessible::TakeSelection() {
  LocalAccessible* select = nsAccUtils::GetSelectableContainer(this, State());
  if (select) {
    if (select->State() & states::MULTISELECTABLE) select->UnselectAll();
    SetSelected(true);
  }
}

void LocalAccessible::TakeFocus() const {
  nsIFrame* frame = GetFrame();
  if (!frame) return;

  nsIContent* focusContent = mContent;

  // If the accessible focus is managed by container widget then focus the
  // widget and set the accessible as its current item.
  if (!frame->IsFocusable()) {
    LocalAccessible* widget = ContainerWidget();
    if (widget && widget->AreItemsOperable()) {
      nsIContent* widgetElm = widget->GetContent();
      nsIFrame* widgetFrame = widgetElm->GetPrimaryFrame();
      if (widgetFrame && widgetFrame->IsFocusable()) {
        focusContent = widgetElm;
        widget->SetCurrentItem(this);
      }
    }
  }

  if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
    dom::AutoHandlingUserInputStatePusher inputStatePusher(true);
    // XXXbz: Can we actually have a non-element content here?
    RefPtr<dom::Element> element = dom::Element::FromNodeOrNull(focusContent);
    fm->SetFocus(element, 0);
  }
}

void LocalAccessible::NameFromAssociatedXULLabel(DocAccessible* aDocument,
                                                 nsIContent* aElm,
                                                 nsString& aName) {
  LocalAccessible* label = nullptr;
  XULLabelIterator iter(aDocument, aElm);
  while ((label = iter.Next())) {
    // Check if label's value attribute is used
    label->Elm()->GetAttr(nsGkAtoms::value, aName);
    if (aName.IsEmpty()) {
      // If no value attribute, a non-empty label must contain
      // children that define its text -- possibly using HTML
      nsTextEquivUtils::AppendTextEquivFromContent(label, label->Elm(), &aName);
    }
  }
  aName.CompressWhitespace();
}

void LocalAccessible::XULElmName(DocAccessible* aDocument, nsIContent* aElm,
                                 nsString& aName) {
  /**
   * 3 main cases for XUL Controls to be labeled
   *   1 - control contains label="foo"
   *   2 - non-child label contains control="controlID"
   *        - label has either value="foo" or children
   *   3 - name from subtree; e.g. a child label element
   * Cases 1 and 2 are handled here.
   * Case 3 is handled by GetNameFromSubtree called in NativeName.
   * Once a label is found, the search is discontinued, so a control
   *  that has a label attribute as well as having a label external to
   *  the control that uses the control="controlID" syntax will use
   *  the label attribute for its Name.
   */


  // CASE #1 (via label attribute) -- great majority of the cases
  // Only do this if this is not a select control element, which uses label
  // attribute to indicate, which option is selected.
  nsCOMPtr<nsIDOMXULSelectControlElement> select =
      aElm->AsElement()->AsXULSelectControl();
  if (!select) {
    aElm->AsElement()->GetAttr(nsGkAtoms::label, aName);
  }

  // CASE #2 -- label as <label control="id" ... ></label>
  if (aName.IsEmpty()) {
    NameFromAssociatedXULLabel(aDocument, aElm, aName);
  }

  aName.CompressWhitespace();
}

nsresult LocalAccessible::HandleAccEvent(AccEvent* aEvent) {
  NS_ENSURE_ARG_POINTER(aEvent);

  if (profiler_thread_is_being_profiled_for_markers()) {
    nsAutoCString strEventType;
    GetAccService()->GetStringEventType(aEvent->GetEventType(), strEventType);
    nsAutoCString strMarker;
    strMarker.AppendLiteral("A11y Event - ");
    strMarker.Append(strEventType);
    PROFILER_MARKER_UNTYPED(strMarker, A11Y);
  }

  if (IPCAccessibilityActive() && Document()) {
    DocAccessibleChild* ipcDoc = mDoc->IPCDoc();
    // If ipcDoc is null, we can't fire the event to the client. We shouldn't
    // have fired the event in the first place, since this makes events
    // inconsistent for local and remote documents. To avoid this, don't call
    // nsEventShell::FireEvent on a DocAccessible for which
    // HasLoadState(eTreeConstructed) is false.
    MOZ_ASSERT(ipcDoc);
    if (ipcDoc) {
      uint64_t id = aEvent->GetAccessible()->ID();

      switch (aEvent->GetEventType()) {
        case nsIAccessibleEvent::EVENT_SHOW:
          ipcDoc->ShowEvent(downcast_accEvent(aEvent));
          break;

        case nsIAccessibleEvent::EVENT_HIDE:
          ipcDoc->AppendMutationEventData(
              HideEventData{id, aEvent->IsFromUserInput()});
          break;

        case nsIAccessibleEvent::EVENT_INNER_REORDER:
        case nsIAccessibleEvent::EVENT_REORDER:
          if (IsTable()) {
            SendCache(CacheDomain::Table, CacheUpdateType::Update,
                      /*aAppendEventData*/ true);
          }

#if defined(XP_WIN)
          if (HasOwnContent() && mContent->IsMathMLElement()) {
            // For any change in a MathML subtree, update the innerHTML cache on
            // the root math element.
            for (LocalAccessible* acc = this; acc; acc = acc->LocalParent()) {
              if (acc->HasOwnContent() &&
                  acc->mContent->IsMathMLElement(nsGkAtoms::math)) {
                mDoc->QueueCacheUpdate(acc, CacheDomain::InnerHTML);
              }
            }
          }
#endif  // defined(XP_WIN)

          // reorder events on the application acc aren't necessary to tell the
          // parent about new top level documents.
          if (!aEvent->GetAccessible()->IsApplication()) {
            ipcDoc->AppendMutationEventData(
                ReorderEventData{id, aEvent->GetEventType()});
          }
          break;
        case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
          AccStateChangeEvent* event = downcast_accEvent(aEvent);
          ipcDoc->SendStateChangeEvent(id, event->GetState(),
                                       event->IsStateEnabled());
          break;
        }
        case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
          AccCaretMoveEvent* event = downcast_accEvent(aEvent);
          ipcDoc->SendCaretMoveEvent(
              id, event->GetCaretOffset(), event->IsSelectionCollapsed(),
              event->IsAtEndOfLine(), event->GetGranularity(),
              event->IsFromUserInput());
          break;
        }
        case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
        case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
          AccTextChangeEvent* event = downcast_accEvent(aEvent);
          ipcDoc->AppendMutationEventData(TextChangeEventData{
              id, event->ModifiedText(), event->GetStartOffset(),
              event->GetLength(), event->IsTextInserted(),
              event->IsFromUserInput()});
          break;
        }
        case nsIAccessibleEvent::EVENT_SELECTION:
        case nsIAccessibleEvent::EVENT_SELECTION_ADD:
        case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
          AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
          ipcDoc->SendSelectionEvent(id, selEvent->Widget()->ID(),
                                     aEvent->GetEventType());
          break;
        }
        case nsIAccessibleEvent::EVENT_FOCUS:
          ipcDoc->SendFocusEvent(id);
          break;
        case nsIAccessibleEvent::EVENT_SCROLLING_END:
        case nsIAccessibleEvent::EVENT_SCROLLING: {
          AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
          ipcDoc->SendScrollingEvent(
              id, aEvent->GetEventType(), scrollingEvent->ScrollX(),
              scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(),
              scrollingEvent->MaxScrollY());
          break;
        }
#if !defined(XP_WIN)
        case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
          AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent);
          ipcDoc->SendAnnouncementEvent(id, announcementEvent->Announcement(),
                                        announcementEvent->Priority());
          break;
        }
#endif  // !defined(XP_WIN)
        case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
          AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
          AutoTArray<TextRange, 1> ranges;
          textSelChangeEvent->SelectionRanges(&ranges);
          nsTArray<TextRangeData> textRangeData(ranges.Length());
          for (size_t i = 0; i < ranges.Length(); i++) {
            const TextRange& range = ranges.ElementAt(i);
            LocalAccessible* start = range.StartContainer()->AsLocal();
            LocalAccessible* end = range.EndContainer()->AsLocal();
            textRangeData.AppendElement(TextRangeData(start->ID(), end->ID(),
                                                      range.StartOffset(),
                                                      range.EndOffset()));
          }
          ipcDoc->SendTextSelectionChangeEvent(id, textRangeData);
          break;
        }
        case nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE:
        case nsIAccessibleEvent::EVENT_NAME_CHANGE: {
          SendCache(CacheDomain::NameAndDescription, CacheUpdateType::Update);
          ipcDoc->SendEvent(id, aEvent->GetEventType());
          break;
        }
        case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
        case nsIAccessibleEvent::EVENT_VALUE_CHANGE: {
          SendCache(CacheDomain::Value, CacheUpdateType::Update);
          ipcDoc->SendEvent(id, aEvent->GetEventType());
          break;
        }
        default:
          ipcDoc->SendEvent(id, aEvent->GetEventType());
      }
    }
  }

  if (nsCoreUtils::AccEventObserversExist()) {
    nsCoreUtils::DispatchAccEvent(MakeXPCEvent(aEvent));
  }

  if (IPCAccessibilityActive()) {
    return NS_OK;
  }

  if (IsDefunct()) {
    // This could happen if there is an XPCOM observer, since script might run
    // which mutates the tree.
    return NS_OK;
  }

  LocalAccessible* target = aEvent->GetAccessible();
  switch (aEvent->GetEventType()) {
    case nsIAccessibleEvent::EVENT_SHOW:
      PlatformShowHideEvent(target, target->LocalParent(), true,
                            aEvent->IsFromUserInput());
      break;
    case nsIAccessibleEvent::EVENT_HIDE:
      PlatformShowHideEvent(target, target->LocalParent(), false,
                            aEvent->IsFromUserInput());
      break;
    case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
      AccStateChangeEvent* event = downcast_accEvent(aEvent);
      PlatformStateChangeEvent(target, event->GetState(),
                               event->IsStateEnabled());
      break;
    }
    case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
      AccCaretMoveEvent* event = downcast_accEvent(aEvent);
      LayoutDeviceIntRect rect;
      // The caret rect is only used on Windows, so just pass an empty rect on
      // other platforms.
      // XXX We pass an empty rect on Windows as well because
      // AccessibleWrap::UpdateSystemCaretFor currently needs to call
      // HyperTextAccessible::GetCaretRect again to get the widget and there's
      // no point calling it twice.
      PlatformCaretMoveEvent(
          target, event->GetCaretOffset(), event->IsSelectionCollapsed(),
          event->GetGranularity(), rect, event->IsFromUserInput());
      break;
    }
    case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
    case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
      AccTextChangeEvent* event = downcast_accEvent(aEvent);
      const nsString& text = event->ModifiedText();
      PlatformTextChangeEvent(target, text, event->GetStartOffset(),
                              event->GetLength(), event->IsTextInserted(),
                              event->IsFromUserInput());
      break;
    }
    case nsIAccessibleEvent::EVENT_SELECTION:
    case nsIAccessibleEvent::EVENT_SELECTION_ADD:
    case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
      AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
      PlatformSelectionEvent(target, selEvent->Widget(),
                             aEvent->GetEventType());
      break;
    }
    case nsIAccessibleEvent::EVENT_FOCUS: {
      LayoutDeviceIntRect rect;
      // The caret rect is only used on Windows, so just pass an empty rect on
      // other platforms.
#ifdef XP_WIN
      if (HyperTextAccessible* text = target->AsHyperText()) {
        nsIWidget* widget = nullptr;
        rect = text->GetCaretRect(&widget);
      }
#endif
      PlatformFocusEvent(target, rect);
      break;
    }
#if defined(ANDROID)
    case nsIAccessibleEvent::EVENT_SCROLLING_END:
    case nsIAccessibleEvent::EVENT_SCROLLING: {
      AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
      PlatformScrollingEvent(
          target, aEvent->GetEventType(), scrollingEvent->ScrollX(),
          scrollingEvent->ScrollY(), scrollingEvent->MaxScrollX(),
          scrollingEvent->MaxScrollY());
      break;
    }
    case nsIAccessibleEvent::EVENT_ANNOUNCEMENT: {
      AccAnnouncementEvent* announcementEvent = downcast_accEvent(aEvent);
      PlatformAnnouncementEvent(target, announcementEvent->Announcement(),
                                announcementEvent->Priority());
      break;
    }
#endif  // defined(ANDROID)
#if defined(MOZ_WIDGET_COCOA)
    case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: {
      AccTextSelChangeEvent* textSelChangeEvent = downcast_accEvent(aEvent);
      AutoTArray<TextRange, 1> ranges;
      textSelChangeEvent->SelectionRanges(&ranges);
      PlatformTextSelectionChangeEvent(target, ranges);
      break;
    }
#endif  // defined(MOZ_WIDGET_COCOA)
    default:
      PlatformEvent(target, aEvent->GetEventType());
  }

  return NS_OK;
}

already_AddRefed<AccAttributes> LocalAccessible::Attributes() {
  RefPtr<AccAttributes> attributes = NativeAttributes();
  if (!HasOwnContent() || !mContent->IsElement()) return attributes.forget();

  // 'xml-roles' attribute coming from ARIA.
  nsString xmlRoles;
  if (nsAccUtils::GetARIAAttr(mContent->AsElement(), nsGkAtoms::role,
                              xmlRoles) &&
      !xmlRoles.IsEmpty()) {
    attributes->SetAttribute(nsGkAtoms::xmlroles, std::move(xmlRoles));
  } else if (nsAtom* landmark = LandmarkRole()) {
    // 'xml-roles' attribute for landmark.
    attributes->SetAttribute(nsGkAtoms::xmlroles, landmark);
  }

  // Expose object attributes from ARIA attributes.
  aria::AttrIterator attribIter(mContent);
  while (attribIter.Next()) {
    if (attribIter.AttrName() == nsGkAtoms::aria_placeholder &&
        attributes->HasAttribute(nsGkAtoms::placeholder)) {
      // If there is an HTML placeholder attribute exposed by
      // HTMLTextFieldAccessible::NativeAttributes, don't expose
      // aria-placeholder.
      continue;
    }
    attribIter.ExposeAttr(attributes);
  }

  // If there is no aria-live attribute then expose default value of 'live'
  // object attribute used for ARIA role of this accessible.
  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
  if (roleMapEntry) {
    if (roleMapEntry->Is(nsGkAtoms::searchbox)) {
      attributes->SetAttribute(nsGkAtoms::textInputType, nsGkAtoms::search);
    }

    if (!attributes->HasAttribute(nsGkAtoms::aria_live)) {
      nsString live;
      if (nsAccUtils::GetLiveAttrValue(roleMapEntry->liveAttRule, live)) {
        attributes->SetAttribute(nsGkAtoms::aria_live, std::move(live));
      }
    }
  }

  return attributes.forget();
}

already_AddRefed<AccAttributes> LocalAccessible::NativeAttributes() {
  RefPtr<AccAttributes> attributes = new AccAttributes();

  // We support values, so expose the string value as well, via the valuetext
  // object attribute. We test for the value interface because we don't want
  // to expose traditional Value() information such as URL's on links and
  // documents, or text in an input.
  if (HasNumericValue()) {
    nsString valuetext;
    Value(valuetext);
    attributes->SetAttribute(nsGkAtoms::aria_valuetext, std::move(valuetext));
  }

  // Expose checkable object attribute if the accessible has checkable state
  if (State() & states::CHECKABLE) {
    attributes->SetAttribute(nsGkAtoms::checkable, true);
  }

  // Expose 'explicit-name' attribute.
  nsAutoString name;
  if (Name(name) != eNameFromSubtree && !name.IsVoid()) {
    attributes->SetAttribute(nsGkAtoms::explicit_name, true);
  }

  bool hierarchical = false;
  uint32_t itemCount = AccGroupInfo::TotalItemCount(this, &hierarchical);
  if (itemCount) {
    attributes->SetAttribute(nsGkAtoms::child_item_count,
                             static_cast<int32_t>(itemCount));
  }

  if (hierarchical) {
    attributes->SetAttribute(nsGkAtoms::tree, true);
  }

  // If the accessible doesn't have own content (such as list item bullet or
  // xul tree item) then don't calculate content based attributes.
  if (!HasOwnContent()) return attributes.forget();

  // Get container-foo computed live region properties based on the closest
  // container with the live region attribute. Inner nodes override outer nodes
  // within the same document. The inner nodes can be used to override live
  // region behavior on more general outer nodes.
  nsAccUtils::SetLiveContainerAttributes(attributes, this);

  if (!mContent->IsElement()) return attributes.forget();

  nsString id;
  if (nsCoreUtils::GetID(mContent, id)) {
    attributes->SetAttribute(nsGkAtoms::id, std::move(id));
  }

  // Expose class because it may have useful microformat information.
  nsString _class;
  if (mContent->AsElement()->GetAttr(nsGkAtoms::_class, _class)) {
    attributes->SetAttribute(nsGkAtoms::_class, std::move(_class));
  }

  // Expose tag.
  attributes->SetAttribute(nsGkAtoms::tag, mContent->NodeInfo()->NameAtom());

  if (auto htmlElement = nsGenericHTMLElement::FromNode(mContent)) {
    // Expose draggable object attribute.
    if (htmlElement->Draggable()) {
      attributes->SetAttribute(nsGkAtoms::draggable, true);
    }
    nsString popover;
    htmlElement->GetPopover(popover);
    if (!popover.IsEmpty()) {
      attributes->SetAttribute(nsGkAtoms::ispopup, std::move(popover));
    }
  }

  // Don't calculate CSS-based object attributes when:
  // 1. There is no frame (e.g. the accessible is unattached from the tree).
  // 2. This is an image map area. CSS is irrelevant here. Furthermore, we won't
  // be able to get the computed style if the map is unslotted in a shadow host.
  nsIFrame* f = mContent->GetPrimaryFrame();
  if (!f || mContent->IsHTMLElement(nsGkAtoms::area)) {
    return attributes.forget();
  }

  // Expose 'display' attribute.
  if (RefPtr<nsAtom> display = DisplayStyle()) {
    attributes->SetAttribute(nsGkAtoms::display, display);
  }

  const ComputedStyle& style = *f->Style();
  auto Atomize = [&](nsCSSPropertyID aId) -> RefPtr<nsAtom> {
    nsAutoCString value;
    style.GetComputedPropertyValue(aId, value);
    return NS_Atomize(value);
  };

  // Expose 'text-align' attribute.
  attributes->SetAttribute(nsGkAtoms::textAlign,
                           Atomize(eCSSProperty_text_align));

  // Expose 'text-indent' attribute.
  attributes->SetAttribute(nsGkAtoms::textIndent,
                           Atomize(eCSSProperty_text_indent));

  auto GetMargin = [&](mozilla::Side aSide) -> CSSCoord {
    // This is here only to guarantee that we do the same as getComputedStyle
    // does, so that we don't hit precision errors in tests.
    const auto& margin = f->StyleMargin()->GetMargin(aSide);
    if (margin.ConvertsToLength()) {
      return margin.AsLengthPercentage().ToLengthInCSSPixels();
    }

    nscoord coordVal = f->GetUsedMargin().Side(aSide);
    return CSSPixel::FromAppUnits(coordVal);
  };

  // Expose 'margin-left' attribute.
  attributes->SetAttribute(nsGkAtoms::marginLeft, GetMargin(eSideLeft));

  // Expose 'margin-right' attribute.
  attributes->SetAttribute(nsGkAtoms::marginRight, GetMargin(eSideRight));

  // Expose 'margin-top' attribute.
  attributes->SetAttribute(nsGkAtoms::marginTop, GetMargin(eSideTop));

  // Expose 'margin-bottom' attribute.
  attributes->SetAttribute(nsGkAtoms::marginBottom, GetMargin(eSideBottom));

  // Expose data-at-shortcutkeys attribute for web applications and virtual
  // cursors. Currently mostly used by JAWS.
  nsString atShortcutKeys;
  if (mContent->AsElement()->GetAttr(
          kNameSpaceID_None, nsGkAtoms::dataAtShortcutkeys, atShortcutKeys)) {
    attributes->SetAttribute(nsGkAtoms::dataAtShortcutkeys,
                             std::move(atShortcutKeys));
  }

  return attributes.forget();
}

bool LocalAccessible::AttributeChangesState(nsAtom* aAttribute) {
  return aAttribute == nsGkAtoms::aria_disabled ||
         // The HTML element disabled state gets handled in
         // DocAccessible::ElementStateChanged. This matches
         // LocalAccessible::NativelyUnavailable.
         (aAttribute == nsGkAtoms::disabled && !mContent->IsHTMLElement()) ||
         aAttribute == nsGkAtoms::tabindex ||
         aAttribute == nsGkAtoms::aria_required ||
         aAttribute == nsGkAtoms::aria_invalid ||
         aAttribute == nsGkAtoms::aria_expanded ||
         aAttribute == nsGkAtoms::aria_checked ||
         (aAttribute == nsGkAtoms::aria_pressed && IsButton()) ||
         aAttribute == nsGkAtoms::aria_readonly ||
         aAttribute == nsGkAtoms::aria_current ||
         aAttribute == nsGkAtoms::aria_haspopup ||
         aAttribute == nsGkAtoms::aria_busy ||
         aAttribute == nsGkAtoms::aria_multiline ||
         aAttribute == nsGkAtoms::aria_multiselectable ||
         // We track this for focusable state update
         aAttribute == nsGkAtoms::contenteditable ||
         aAttribute == nsGkAtoms::popovertarget;
}

void LocalAccessible::DOMAttributeChanged(int32_t aNameSpaceID,
                                          nsAtom* aAttribute, int32_t aModType,
                                          const nsAttrValue* aOldValue,
                                          uint64_t aOldState) {
  // Fire accessible event after short timer, because we need to wait for
  // DOM attribute & resulting layout to actually change. Otherwise,
  // assistive technology will retrieve the wrong state/value/selection info.

  CssAltContent::HandleAttributeChange(mContent, aNameSpaceID, aAttribute);

  // XXX todo
  // We still need to handle special HTML cases here
  // For example, if an <img>'s usemap attribute is modified
  // Otherwise it may just be a state change, for example an object changing
  // its visibility
  //
  // XXX todo: report aria state changes for "undefined" literal value changes
  // filed as bug 472142
  //
  // XXX todo:  invalidate accessible when aria state changes affect exposed
  // role filed as bug 472143

  if (AttributeChangesState(aAttribute)) {
    uint64_t currState = State();
    uint64_t diffState = currState ^ aOldState;
    if (diffState) {
      for (uint64_t state = 1; state <= states::LAST_ENTRY; state <<= 1) {
        if (diffState & state) {
          RefPtr<AccEvent> stateChangeEvent =
              new AccStateChangeEvent(this, state, (currState & state));
          mDoc->FireDelayedEvent(stateChangeEvent);
        }
      }
    }
  }

  if (aAttribute == nsGkAtoms::_class) {
    mDoc->QueueCacheUpdate(this, CacheDomain::DOMNodeIDAndClass);
    return;
  }

  // When a details object has its open attribute changed
  // we should fire a state-change event on the accessible of
  // its main summary
  if (aAttribute == nsGkAtoms::open) {
    // FromDetails checks if the given accessible belongs to
    // a details frame and also locates the accessible of its
    // main summary.
    if (HTMLSummaryAccessible* summaryAccessible =
            HTMLSummaryAccessible::FromDetails(this)) {
      RefPtr<AccEvent> expandedChangeEvent =
          new AccStateChangeEvent(summaryAccessible, states::EXPANDED);
      mDoc->FireDelayedEvent(expandedChangeEvent);
      return;
    }
  }

  // Check for namespaced ARIA attribute
  if (aNameSpaceID == kNameSpaceID_None) {
    // Check for hyphenated aria-foo property?
    if (StringBeginsWith(nsDependentAtomString(aAttribute), u"aria-"_ns)) {
      uint8_t attrFlags = aria::AttrCharacteristicsFor(aAttribute);
      if (!(attrFlags & ATTR_BYPASSOBJ)) {
        mDoc->QueueCacheUpdate(this, CacheDomain::ARIA);
        // For aria attributes like drag and drop changes we fire a generic
        // attribute change event; at least until native API comes up with a
        // more meaningful event.
        RefPtr<AccEvent> event =
            new AccObjectAttrChangedEvent(this, aAttribute);
        mDoc->FireDelayedEvent(event);
      }
    }
  }

  dom::Element* elm = Elm();

  if (HasNumericValue() &&
      (aAttribute == nsGkAtoms::aria_valuemax ||
       aAttribute == nsGkAtoms::aria_valuemin || aAttribute == nsGkAtoms::min ||
       aAttribute == nsGkAtoms::max || aAttribute == nsGkAtoms::step)) {
    mDoc->QueueCacheUpdate(this, CacheDomain::Value);
    return;
  }

  // Fire text value change event whenever aria-valuetext is changed.
  if (aAttribute == nsGkAtoms::aria_valuetext) {
    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE, this);
    return;
  }

  if (aAttribute == nsGkAtoms::aria_valuenow) {
    if (!nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_valuetext) ||
        nsAccUtils::ARIAAttrValueIs(elm, nsGkAtoms::aria_valuetext,
                                    nsGkAtoms::_empty, eCaseMatters)) {
      // Fire numeric value change event when aria-valuenow is changed and
      // aria-valuetext is empty
      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, this);
    } else {
      // We need to update the cache here since we won't get an event if
      // aria-valuenow is shadowed by aria-valuetext.
      mDoc->QueueCacheUpdate(this, CacheDomain::Value);
    }
    return;
  }

  if (aAttribute == nsGkAtoms::aria_owns) {
    mDoc->Controller()->ScheduleRelocation(this);
  }

  // Fire name change and description change events.
  if (aAttribute == nsGkAtoms::aria_label) {
    // A valid aria-labelledby would take precedence so an aria-label change
    // won't change the name.
    AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
    if (!iter.NextElem()) {
      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
    }
    return;
  }

  if (aAttribute == nsGkAtoms::aria_description) {
    // A valid aria-describedby would take precedence so an aria-description
    // change won't change the description.
    AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
    if (!iter.NextElem()) {
      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
                             this);
    }
    return;
  }

  if (aAttribute == nsGkAtoms::aria_describedby) {
    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE, this);
    if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
        aModType == dom::MutationEvent_Binding::ADDITION) {
      // The subtrees of the new aria-describedby targets might be used to
      // compute the description for this. Therefore, we need to set
      // the eHasDescriptionDependent flag on all Accessibles in these subtrees.
      AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_describedby);
      while (LocalAccessible* target = iter.Next()) {
        target->ModifySubtreeContextFlags(eHasDescriptionDependent, true);
      }
    }
    return;
  }

  if (aAttribute == nsGkAtoms::aria_labelledby) {
    // We only queue cache updates for explicit relations. Implicit, reverse
    // relations are handled in ApplyCache and stored in a map on the remote
    // document itself.
    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
    if (aModType == dom::MutationEvent_Binding::MODIFICATION ||
        aModType == dom::MutationEvent_Binding::ADDITION) {
      // The subtrees of the new aria-labelledby targets might be used to
      // compute the name for this. Therefore, we need to set
      // the eHasNameDependent flag on all Accessibles in these subtrees.
      AssociatedElementsIterator iter(mDoc, elm, nsGkAtoms::aria_labelledby);
      while (LocalAccessible* target = iter.Next()) {
        target->ModifySubtreeContextFlags(eHasNameDependent, true);
      }
    }
    return;
  }

  if ((aAttribute == nsGkAtoms::aria_expanded ||
       aAttribute == nsGkAtoms::href) &&
      (aModType == dom::MutationEvent_Binding::ADDITION ||
       aModType == dom::MutationEvent_Binding::REMOVAL)) {
    // The presence of aria-expanded adds an expand/collapse action.
    mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
  }

  if (aAttribute == nsGkAtoms::href || aAttribute == nsGkAtoms::src) {
    mDoc->QueueCacheUpdate(this, CacheDomain::Value);
  }

  if (aAttribute == nsGkAtoms::aria_controls ||
      aAttribute == nsGkAtoms::aria_flowto ||
      aAttribute == nsGkAtoms::aria_details ||
      aAttribute == nsGkAtoms::aria_errormessage) {
    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
  }

  if (aAttribute == nsGkAtoms::popovertarget) {
    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
    return;
  }

  if (aAttribute == nsGkAtoms::alt &&
      !nsAccUtils::HasARIAAttr(elm, nsGkAtoms::aria_label) &&
      !elm->HasAttr(nsGkAtoms::aria_labelledby)) {
    mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
    return;
  }

  if (aAttribute == nsGkAtoms::title) {
    nsAutoString name;
    ARIAName(name);
    if (name.IsEmpty()) {
      NativeName(name);
      if (name.IsEmpty()) {
        mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, this);
        return;
      }
    }

    if (!elm->HasAttr(nsGkAtoms::aria_describedby)) {
      mDoc->FireDelayedEvent(nsIAccessibleEvent::EVENT_DESCRIPTION_CHANGE,
                             this);
    }

    return;
  }

  // ARIA or XUL selection
  if ((mContent->IsXULElement() && aAttribute == nsGkAtoms::selected) ||
      aAttribute == nsGkAtoms::aria_selected) {
    LocalAccessible* widget = nsAccUtils::GetSelectableContainer(this, State());
    if (widget) {
      AccSelChangeEvent::SelChangeType selChangeType;
      if (aNameSpaceID != kNameSpaceID_None) {
        selChangeType = elm->AttrValueIs(aNameSpaceID, aAttribute,
                                         nsGkAtoms::_true, eCaseMatters)
                            ? AccSelChangeEvent::eSelectionAdd
                            : AccSelChangeEvent::eSelectionRemove;
      } else {
        selChangeType = nsAccUtils::ARIAAttrValueIs(
                            elm, aAttribute, nsGkAtoms::_true, eCaseMatters)
                            ? AccSelChangeEvent::eSelectionAdd
                            : AccSelChangeEvent::eSelectionRemove;
      }

      RefPtr<AccEvent> event =
          new AccSelChangeEvent(widget, this, selChangeType);
      mDoc->FireDelayedEvent(event);
      if (aAttribute == nsGkAtoms::aria_selected) {
        mDoc->QueueCacheUpdate(this, CacheDomain::State);
      }
    }

    return;
  }

  if (aAttribute == nsGkAtoms::aria_level ||
      aAttribute == nsGkAtoms::aria_setsize ||
      aAttribute == nsGkAtoms::aria_posinset) {
    mDoc->QueueCacheUpdate(this, CacheDomain::GroupInfo);
    return;
  }

  if (aAttribute == nsGkAtoms::accesskey) {
    mDoc->QueueCacheUpdate(this, CacheDomain::Actions);
  }

  if (aAttribute == nsGkAtoms::name &&
      (mContent && mContent->IsHTMLElement(nsGkAtoms::a))) {
    // If an anchor's name changed, it's possible a LINKS_TO relation
    // also changed. Push a cache update for Relations.
    mDoc->QueueCacheUpdate(this, CacheDomain::Relations);
  }
}

void LocalAccessible::ARIAGroupPosition(int32_t* aLevel, int32_t* aSetSize,
                                        int32_t* aPosInSet) const {
  if (!mContent) {
    return;
  }

  if (aLevel) {
    nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_level, aLevel);
  }
  if (aSetSize) {
    nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_setsize, aSetSize);
  }
  if (aPosInSet) {
    nsCoreUtils::GetUIntAttr(mContent, nsGkAtoms::aria_posinset, aPosInSet);
  }
}

uint64_t LocalAccessible::State() {
  if (IsDefunct()) return states::DEFUNCT;

  uint64_t state = NativeState();
  // Apply ARIA states to be sure accessible states will be overridden.
  ApplyARIAState(&state);

  const uint32_t kExpandCollapseStates = states::COLLAPSED | states::EXPANDED;
  if ((state & kExpandCollapseStates) == kExpandCollapseStates) {
    // Cannot be both expanded and collapsed -- this happens in ARIA expanded
    // combobox because of limitation of ARIAMap.
    // XXX: Perhaps we will be able to make this less hacky if we support
    // extended states in ARIAMap, e.g. derive COLLAPSED from
    // EXPANDABLE && !EXPANDED.
    state &= ~states::COLLAPSED;
  }

  if (!(state & states::UNAVAILABLE)) {
    state |= states::ENABLED | states::SENSITIVE;

    // If the object is a current item of container widget then mark it as
    // ACTIVE. This allows screen reader virtual buffer modes to know which
    // descendant is the current one that would get focus if the user navigates
    // to the container widget.
    LocalAccessible* widget = ContainerWidget();
    if (widget && widget->CurrentItem() == this) state |= states::ACTIVE;
  }

  if ((state & states::COLLAPSED) || (state & states::EXPANDED)) {
    state |= states::EXPANDABLE;
  }

  ApplyImplicitState(state);
  return state;
}

void LocalAccessible::ApplyARIAState(uint64_t* aState) const {
  if (!mContent->IsElement()) return;

  dom::Element* element = mContent->AsElement();

  // Test for universal states first
  *aState |= aria::UniversalStatesFor(element);

  const nsRoleMapEntry* roleMapEntry = ARIARoleMap();
  if (!roleMapEntry && IsHTMLTableCell() && Role() == roles::GRID_CELL) {
    // This is a <td> inside a role="grid", so it gets an implicit role of
    // GRID_CELL in ARIATransformRole. However, because it's implicit, we
    // don't have a role map entry, and without that, we can't apply ARIA states
    // below. Therefore, we get the role map entry here.
    roleMapEntry = aria::GetRoleMap(nsGkAtoms::gridcell);
    MOZ_ASSERT(roleMapEntry, "Should have role map entry for gridcell");
  }
  if (roleMapEntry) {
    // We only force the readonly bit off if we have a real mapping for the aria
    // role. This preserves the ability for screen readers to use readonly
    // (primarily on the document) as the hint for creating a virtual buffer.
    if (roleMapEntry->role != roles::NOTHING) *aState &= ~states::READONLY;

    if (mContent->HasID()) {
      // If has a role & ID and aria-activedescendant on the container, assume
      // focusable.
      const LocalAccessible* ancestor = this;
      while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
        dom::Element* el = ancestor->Elm();
        if (el && el->HasAttr(nsGkAtoms::aria_activedescendant)) {
          *aState |= states::FOCUSABLE;
          break;
        }
      }
    }
  }

  if (*aState & states::FOCUSABLE) {
    // Propogate aria-disabled from ancestors down to any focusable descendant.
    const LocalAccessible* ancestor = this;
    while ((ancestor = ancestor->LocalParent()) && !ancestor->IsDoc()) {
      dom::Element* el = ancestor->Elm();
      if (el && nsAccUtils::ARIAAttrValueIs(el, nsGkAtoms::aria_disabled,
                                            nsGkAtoms::_true, eCaseMatters)) {
        *aState |= states::UNAVAILABLE;
        break;
      }
    }
  } else {
    // Sometimes, we use aria-activedescendant targeting something which isn't
    // actually a descendant. This is technically a spec violation, but it's a
    // useful hack which makes certain things much easier. For example, we use
    // this for "fake focus" for multi select browser tabs and Quantumbar
    // autocomplete suggestions.
    // In these cases, the aria-activedescendant code above won't make the
    // active item focusable. It doesn't make sense for something to have
    // focus when it isn't focusable, so fix that here.
    if (FocusMgr()->IsActiveItem(this)) {
      *aState |= states::FOCUSABLE;
    }
  }

  // special case: A native button element whose role got transformed by ARIA to
  // a toggle button Also applies to togglable button menus, like in the Dev
  // Tools Web Console.
  if (IsButton() || IsMenuButton()) {
    aria::MapToState(aria::eARIAPressed, element, aState);
  }

  if (!roleMapEntry) return;

  *aState |= roleMapEntry->state;

  if (aria::MapToState(roleMapEntry->attributeMap1, element, aState) &&
      aria::MapToState(roleMapEntry->attributeMap2, element, aState) &&
      aria::MapToState(roleMapEntry->attributeMap3, element, aState)) {
    aria::MapToState(roleMapEntry->attributeMap4, element, aState);
  }

  // ARIA gridcell inherits readonly state from the grid until it's overridden.
  if ((roleMapEntry->Is(nsGkAtoms::gridcell) ||
       roleMapEntry->Is(nsGkAtoms::columnheader) ||
       roleMapEntry->Is(nsGkAtoms::rowheader)) &&
      // Don't recurse infinitely for an authoring error like
      // <table role="gridcell">. Without this check, we'd call TableFor(this)
      // below, which would return this.
      !IsTable() &&
      !nsAccUtils::HasDefinedARIAToken(mContent, nsGkAtoms::aria_readonly)) {
    if (const LocalAccessible* grid = nsAccUtils::TableFor(this)) {
      uint64_t gridState = 0;
      grid->ApplyARIAState(&gridState);
      *aState |= gridState & states::READONLY;
    }
  }
}

void LocalAccessible::Value(nsString& aValue) const {
  if (HasNumericValue()) {
    // aria-valuenow is a number, and aria-valuetext is the optional text
    // equivalent. For the string value, we will try the optional text
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=87 H=96 G=91

¤ Dauer der Verarbeitung: 0.56 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge