import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

1008
accessible/base/ARIAMap.cpp Normal file

File diff suppressed because it is too large Load diff

305
accessible/base/ARIAMap.h Normal file
View file

@ -0,0 +1,305 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=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/. */
#ifndef mozilla_a11y_aria_ARIAMap_h_
#define mozilla_a11y_aria_ARIAMap_h_
#include "ARIAStateMap.h"
#include "mozilla/a11y/AccTypes.h"
#include "mozilla/a11y/Role.h"
#include "nsIAtom.h"
#include "nsIContent.h"
class nsINode;
////////////////////////////////////////////////////////////////////////////////
// Value constants
/**
* Used to define if role requires to expose Value interface.
*/
enum EValueRule
{
/**
* Value interface isn't exposed.
*/
eNoValue,
/**
* Value interface is implemented, supports value, min and max from
* aria-valuenow, aria-valuemin and aria-valuemax.
*/
eHasValueMinMax
};
////////////////////////////////////////////////////////////////////////////////
// Action constants
/**
* Used to define if the role requires to expose action.
*/
enum EActionRule
{
eNoAction,
eActivateAction,
eClickAction,
ePressAction,
eCheckUncheckAction,
eExpandAction,
eJumpAction,
eOpenCloseAction,
eSelectAction,
eSortAction,
eSwitchAction
};
////////////////////////////////////////////////////////////////////////////////
// Live region constants
/**
* Used to define if role exposes default value of aria-live attribute.
*/
enum ELiveAttrRule
{
eNoLiveAttr,
eOffLiveAttr,
ePoliteLiveAttr
};
////////////////////////////////////////////////////////////////////////////////
// Role constants
/**
* ARIA role overrides role from native markup.
*/
const bool kUseMapRole = true;
/**
* ARIA role doesn't override the role from native markup.
*/
const bool kUseNativeRole = false;
////////////////////////////////////////////////////////////////////////////////
// ARIA attribute characteristic masks
/**
* This mask indicates the attribute should not be exposed as an object
* attribute via the catch-all logic in Accessible::Attributes().
* This means it either isn't mean't to be exposed as an object attribute, or
* that it should, but is already handled in other code.
*/
const uint8_t ATTR_BYPASSOBJ = 0x1 << 0;
const uint8_t ATTR_BYPASSOBJ_IF_FALSE = 0x1 << 1;
/**
* This mask indicates the attribute is expected to have an NMTOKEN or bool value.
* (See for example usage in Accessible::Attributes())
*/
const uint8_t ATTR_VALTOKEN = 0x1 << 2;
/**
* Indicate the attribute is global state or property (refer to
* http://www.w3.org/TR/wai-aria/states_and_properties#global_states).
*/
const uint8_t ATTR_GLOBAL = 0x1 << 3;
////////////////////////////////////////////////////////////////////////////////
// State map entry
/**
* Used in nsRoleMapEntry.state if no nsIAccessibleStates are automatic for
* a given role.
*/
#define kNoReqStates 0
////////////////////////////////////////////////////////////////////////////////
// Role map entry
/**
* For each ARIA role, this maps the nsIAccessible information.
*/
struct nsRoleMapEntry
{
/**
* Return true if matches to the given ARIA role.
*/
bool Is(nsIAtom* aARIARole) const
{ return *roleAtom == aARIARole; }
/**
* Return true if ARIA role has the given accessible type.
*/
bool IsOfType(mozilla::a11y::AccGenericType aType) const
{ return accTypes & aType; }
/**
* Return ARIA role.
*/
const nsDependentAtomString ARIARoleString() const
{ return nsDependentAtomString(*roleAtom); }
// ARIA role: string representation such as "button"
nsIAtom** roleAtom;
// Role mapping rule: maps to enum Role
mozilla::a11y::role role;
// Role rule: whether to use mapped role or native semantics
bool roleRule;
// Value mapping rule: how to compute accessible value
EValueRule valueRule;
// Action mapping rule, how to expose accessible action
EActionRule actionRule;
// 'live' and 'container-live' object attributes mapping rule: how to expose
// these object attributes if ARIA 'live' attribute is missed.
ELiveAttrRule liveAttRule;
// Accessible types this role belongs to.
uint32_t accTypes;
// Automatic state mapping rule: always include in states
uint64_t state; // or kNoReqStates if no default state for this role
// ARIA properties supported for this role (in other words, the aria-foo
// attribute to accessible states mapping rules).
// Currently you cannot have unlimited mappings, because
// a variable sized array would not allow the use of
// C++'s struct initialization feature.
mozilla::a11y::aria::EStateRule attributeMap1;
mozilla::a11y::aria::EStateRule attributeMap2;
mozilla::a11y::aria::EStateRule attributeMap3;
mozilla::a11y::aria::EStateRule attributeMap4;
};
////////////////////////////////////////////////////////////////////////////////
// ARIA map
/**
* These provide the mappings for WAI-ARIA roles, states and properties using
* the structs defined in this file and ARIAStateMap files.
*/
namespace mozilla {
namespace a11y {
namespace aria {
/**
* Empty role map entry. Used by accessibility service to create an accessible
* if the accessible can't use role of used accessible class. For example,
* it is used for table cells that aren't contained by table.
*/
extern nsRoleMapEntry gEmptyRoleMap;
/**
* Constants for the role map entry index to indicate that the role map entry
* isn't in sWAIRoleMaps, but rather is a special entry: nullptr,
* gEmptyRoleMap, and sLandmarkRoleMap
*/
const uint8_t NO_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 2;
const uint8_t EMPTY_ROLE_MAP_ENTRY_INDEX = UINT8_MAX - 1;
const uint8_t LANDMARK_ROLE_MAP_ENTRY_INDEX = UINT8_MAX;
/**
* Get the role map entry for a given DOM node. This will use the first
* ARIA role if the role attribute provides a space delimited list of roles.
*
* @param aEl [in] the DOM node to get the role map entry for
* @return a pointer to the role map entry for the ARIA role, or nullptr
* if none
*/
const nsRoleMapEntry* GetRoleMap(dom::Element* aEl);
/**
* Get the role map entry pointer's index for a given DOM node. This will use
* the first ARIA role if the role attribute provides a space delimited list of
* roles.
*
* @param aEl [in] the DOM node to get the role map entry for
* @return the index of the pointer to the role map entry for the ARIA
* role, or NO_ROLE_MAP_ENTRY_INDEX if none
*/
uint8_t GetRoleMapIndex(dom::Element* aEl);
/**
* Get the role map entry pointer for a given role map entry index.
*
* @param aRoleMapIndex [in] the role map index to get the role map entry
* pointer for
* @return a pointer to the role map entry for the ARIA role,
* or nullptr, if none
*/
const nsRoleMapEntry* GetRoleMapFromIndex(uint8_t aRoleMapIndex);
/**
* Get the role map entry index for a given role map entry pointer. If the role
* map entry is within sWAIRoleMaps, return the index within that array,
* otherwise return one of the special index constants listed above.
*
* @param aRoleMap [in] the role map entry pointer to get the index for
* @return the index of the pointer to the role map entry, or
* NO_ROLE_MAP_ENTRY_INDEX if none
*/
uint8_t GetIndexFromRoleMap(const nsRoleMapEntry* aRoleMap);
/**
* Return accessible state from ARIA universal states applied to the given
* element.
*/
uint64_t UniversalStatesFor(mozilla::dom::Element* aElement);
/**
* Get the ARIA attribute characteristics for a given ARIA attribute.
*
* @param aAtom ARIA attribute
* @return A bitflag representing the attribute characteristics
* (see above for possible bit masks, prefixed "ATTR_")
*/
uint8_t AttrCharacteristicsFor(nsIAtom* aAtom);
/**
* Return true if the element has defined aria-hidden.
*/
bool HasDefinedARIAHidden(nsIContent* aContent);
/**
* Represents a simple enumerator for iterating through ARIA attributes
* exposed as object attributes on a given accessible.
*/
class AttrIterator
{
public:
explicit AttrIterator(nsIContent* aContent) :
mContent(aContent), mAttrIdx(0)
{
mAttrCount = mContent->GetAttrCount();
}
bool Next(nsAString& aAttrName, nsAString& aAttrValue);
private:
AttrIterator() = delete;
AttrIterator(const AttrIterator&) = delete;
AttrIterator& operator= (const AttrIterator&) = delete;
nsIContent* mContent;
uint32_t mAttrIdx;
uint32_t mAttrCount;
};
} // namespace aria
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,376 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "ARIAMap.h"
#include "nsAccUtils.h"
#include "States.h"
#include "mozilla/dom/Element.h"
using namespace mozilla;
using namespace mozilla::a11y;
using namespace mozilla::a11y::aria;
/**
* Used to store state map rule data for ARIA attribute of enum type.
*/
struct EnumTypeData
{
// ARIA attribute name.
nsIAtom* const mAttrName;
// States if the attribute value is matched to the enum value. Used as
// nsIContent::AttrValuesArray, last item must be nullptr.
nsIAtom* const* const mValues[4];
// States applied if corresponding enum values are matched.
const uint64_t mStates[3];
// States to clear in case of match.
const uint64_t mClearState;
};
enum ETokenType
{
eBoolType = 0,
eMixedType = 1, // can take 'mixed' value
eDefinedIfAbsent = 2 // permanent and false state are applied if absent
};
/**
* Used to store state map rule data for ARIA attribute of token type (including
* mixed value).
*/
struct TokenTypeData
{
TokenTypeData(nsIAtom* aAttrName, uint32_t aType,
uint64_t aPermanentState,
uint64_t aTrueState,
uint64_t aFalseState = 0) :
mAttrName(aAttrName), mType(aType), mPermanentState(aPermanentState),
mTrueState(aTrueState), mFalseState(aFalseState)
{ }
// ARIA attribute name.
nsIAtom* const mAttrName;
// Type.
const uint32_t mType;
// State applied if the attribute is defined or mType doesn't have
// eDefinedIfAbsent flag set.
const uint64_t mPermanentState;
// States applied if the attribute value is true/false.
const uint64_t mTrueState;
const uint64_t mFalseState;
};
/**
* Map enum type attribute value to accessible state.
*/
static void MapEnumType(dom::Element* aElement, uint64_t* aState,
const EnumTypeData& aData);
/**
* Map token type attribute value to states.
*/
static void MapTokenType(dom::Element* aContent, uint64_t* aState,
const TokenTypeData& aData);
bool
aria::MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState)
{
switch (aRule) {
case eARIAAutoComplete:
{
static const EnumTypeData data = {
nsGkAtoms::aria_autocomplete,
{ &nsGkAtoms::inlinevalue,
&nsGkAtoms::list,
&nsGkAtoms::both, nullptr },
{ states::SUPPORTS_AUTOCOMPLETION,
states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION,
states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION }, 0
};
MapEnumType(aElement, aState, data);
return true;
}
case eARIABusy:
{
static const EnumTypeData data = {
nsGkAtoms::aria_busy,
{ &nsGkAtoms::_true,
&nsGkAtoms::error, nullptr },
{ states::BUSY,
states::INVALID }, 0
};
MapEnumType(aElement, aState, data);
return true;
}
case eARIACheckableBool:
{
static const TokenTypeData data(
nsGkAtoms::aria_checked, eBoolType | eDefinedIfAbsent,
states::CHECKABLE, states::CHECKED);
MapTokenType(aElement, aState, data);
return true;
}
case eARIACheckableMixed:
{
static const TokenTypeData data(
nsGkAtoms::aria_checked, eMixedType | eDefinedIfAbsent,
states::CHECKABLE, states::CHECKED);
MapTokenType(aElement, aState, data);
return true;
}
case eARIACheckedMixed:
{
static const TokenTypeData data(
nsGkAtoms::aria_checked, eMixedType,
states::CHECKABLE, states::CHECKED);
MapTokenType(aElement, aState, data);
return true;
}
case eARIADisabled:
{
static const TokenTypeData data(
nsGkAtoms::aria_disabled, eBoolType,
0, states::UNAVAILABLE);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAExpanded:
{
static const TokenTypeData data(
nsGkAtoms::aria_expanded, eBoolType,
0, states::EXPANDED, states::COLLAPSED);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAHasPopup:
{
static const TokenTypeData data(
nsGkAtoms::aria_haspopup, eBoolType,
0, states::HASPOPUP);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAInvalid:
{
static const TokenTypeData data(
nsGkAtoms::aria_invalid, eBoolType,
0, states::INVALID);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAModal:
{
static const TokenTypeData data(
nsGkAtoms::aria_modal, eBoolType,
0, states::MODAL);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAMultiline:
{
static const TokenTypeData data(
nsGkAtoms::aria_multiline, eBoolType | eDefinedIfAbsent,
0, states::MULTI_LINE, states::SINGLE_LINE);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAMultiSelectable:
{
static const TokenTypeData data(
nsGkAtoms::aria_multiselectable, eBoolType,
0, states::MULTISELECTABLE | states::EXTSELECTABLE);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAOrientation:
{
static const EnumTypeData data = {
nsGkAtoms::aria_orientation,
{ &nsGkAtoms::horizontal,
&nsGkAtoms::vertical, nullptr },
{ states::HORIZONTAL,
states::VERTICAL },
states::HORIZONTAL | states::VERTICAL
};
MapEnumType(aElement, aState, data);
return true;
}
case eARIAPressed:
{
static const TokenTypeData data(
nsGkAtoms::aria_pressed, eMixedType,
0, states::PRESSED);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAReadonly:
{
static const TokenTypeData data(
nsGkAtoms::aria_readonly, eBoolType,
0, states::READONLY);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAReadonlyOrEditable:
{
static const TokenTypeData data(
nsGkAtoms::aria_readonly, eBoolType | eDefinedIfAbsent,
0, states::READONLY, states::EDITABLE);
MapTokenType(aElement, aState, data);
return true;
}
case eARIAReadonlyOrEditableIfDefined:
{
static const TokenTypeData data(
nsGkAtoms::aria_readonly, eBoolType,
0, states::READONLY, states::EDITABLE);
MapTokenType(aElement, aState, data);
return true;
}
case eARIARequired:
{
static const TokenTypeData data(
nsGkAtoms::aria_required, eBoolType,
0, states::REQUIRED);
MapTokenType(aElement, aState, data);
return true;
}
case eARIASelectable:
{
static const TokenTypeData data(
nsGkAtoms::aria_selected, eBoolType | eDefinedIfAbsent,
states::SELECTABLE, states::SELECTED);
MapTokenType(aElement, aState, data);
return true;
}
case eARIASelectableIfDefined:
{
static const TokenTypeData data(
nsGkAtoms::aria_selected, eBoolType,
states::SELECTABLE, states::SELECTED);
MapTokenType(aElement, aState, data);
return true;
}
case eReadonlyUntilEditable:
{
if (!(*aState & states::EDITABLE))
*aState |= states::READONLY;
return true;
}
case eIndeterminateIfNoValue:
{
if (!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuenow) &&
!aElement->HasAttr(kNameSpaceID_None, nsGkAtoms::aria_valuetext))
*aState |= states::MIXED;
return true;
}
case eFocusableUntilDisabled:
{
if (!nsAccUtils::HasDefinedARIAToken(aElement, nsGkAtoms::aria_disabled) ||
aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
nsGkAtoms::_false, eCaseMatters))
*aState |= states::FOCUSABLE;
return true;
}
default:
return false;
}
}
static void
MapEnumType(dom::Element* aElement, uint64_t* aState, const EnumTypeData& aData)
{
switch (aElement->FindAttrValueIn(kNameSpaceID_None, aData.mAttrName,
aData.mValues, eCaseMatters)) {
case 0:
*aState = (*aState & ~aData.mClearState) | aData.mStates[0];
return;
case 1:
*aState = (*aState & ~aData.mClearState) | aData.mStates[1];
return;
case 2:
*aState = (*aState & ~aData.mClearState) | aData.mStates[2];
return;
}
}
static void
MapTokenType(dom::Element* aElement, uint64_t* aState,
const TokenTypeData& aData)
{
if (nsAccUtils::HasDefinedARIAToken(aElement, aData.mAttrName)) {
if ((aData.mType & eMixedType) &&
aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
nsGkAtoms::mixed, eCaseMatters)) {
*aState |= aData.mPermanentState | states::MIXED;
return;
}
if (aElement->AttrValueIs(kNameSpaceID_None, aData.mAttrName,
nsGkAtoms::_false, eCaseMatters)) {
*aState |= aData.mPermanentState | aData.mFalseState;
return;
}
*aState |= aData.mPermanentState | aData.mTrueState;
return;
}
if (aData.mType & eDefinedIfAbsent)
*aState |= aData.mPermanentState | aData.mFalseState;
}

View file

@ -0,0 +1,67 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef _mozilla_a11y_aria_ARIAStateMap_h_
#define _mozilla_a11y_aria_ARIAStateMap_h_
#include <stdint.h>
namespace mozilla {
namespace dom {
class Element;
}
namespace a11y {
namespace aria {
/**
* List of the ARIA state mapping rules.
*/
enum EStateRule
{
eARIANone,
eARIAAutoComplete,
eARIABusy,
eARIACheckableBool,
eARIACheckableMixed,
eARIACheckedMixed,
eARIADisabled,
eARIAExpanded,
eARIAHasPopup,
eARIAInvalid,
eARIAModal,
eARIAMultiline,
eARIAMultiSelectable,
eARIAOrientation,
eARIAPressed,
eARIAReadonly,
eARIAReadonlyOrEditable,
eARIAReadonlyOrEditableIfDefined,
eARIARequired,
eARIASelectable,
eARIASelectableIfDefined,
eReadonlyUntilEditable,
eIndeterminateIfNoValue,
eFocusableUntilDisabled
};
/**
* Expose the accessible states for the given element accordingly to state
* mapping rule.
*
* @param aRule [in] state mapping rule ID
* @param aElement [in] node of the accessible
* @param aState [in/out] accessible states
* @return true if state map rule ID is valid
*/
bool MapToState(EStateRule aRule, dom::Element* aElement, uint64_t* aState);
} // namespace aria
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,271 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "nsAccUtils.h"
#include "DocAccessible.h"
#include "xpcAccEvents.h"
#include "States.h"
#include "xpcAccessibleDocument.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/dom/Selection.h"
using namespace mozilla;
using namespace mozilla::a11y;
static_assert(static_cast<bool>(eNoUserInput) == false &&
static_cast<bool>(eFromUserInput) == true,
"EIsFromUserInput cannot be casted to bool");
////////////////////////////////////////////////////////////////////////////////
// AccEvent
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// AccEvent constructors
AccEvent::AccEvent(uint32_t aEventType, Accessible* aAccessible,
EIsFromUserInput aIsFromUserInput, EEventRule aEventRule) :
mEventType(aEventType), mEventRule(aEventRule), mAccessible(aAccessible)
{
if (aIsFromUserInput == eAutoDetect)
mIsFromUserInput = EventStateManager::IsHandlingUserInput();
else
mIsFromUserInput = aIsFromUserInput == eFromUserInput ? true : false;
}
////////////////////////////////////////////////////////////////////////////////
// AccEvent cycle collection
NS_IMPL_CYCLE_COLLECTION_CLASS(AccEvent)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AccEvent)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAccessible)
if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) {
tmEvent->SetNextEvent(nullptr);
tmEvent->SetPrevEvent(nullptr);
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AccEvent)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAccessible)
if (AccTreeMutationEvent* tmEvent = downcast_accEvent(tmp)) {
CycleCollectionNoteChild(cb, tmEvent->NextEvent(), "mNext");
CycleCollectionNoteChild(cb, tmEvent->PrevEvent(), "mPrevEvent");
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AccEvent, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AccEvent, Release)
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// AccTextChangeEvent
////////////////////////////////////////////////////////////////////////////////
// Note: we pass in eAllowDupes to the base class because we don't support text
// events coalescence. We fire delayed text change events in DocAccessible but
// we continue to base the event off the accessible object rather than just the
// node. This means we won't try to create an accessible based on the node when
// we are ready to fire the event and so we will no longer assert at that point
// if the node was removed from the document. Either way, the AT won't work with
// a defunct accessible so the behaviour should be equivalent.
AccTextChangeEvent::
AccTextChangeEvent(Accessible* aAccessible, int32_t aStart,
const nsAString& aModifiedText, bool aIsInserted,
EIsFromUserInput aIsFromUserInput)
: AccEvent(aIsInserted ?
static_cast<uint32_t>(nsIAccessibleEvent::EVENT_TEXT_INSERTED) :
static_cast<uint32_t>(nsIAccessibleEvent::EVENT_TEXT_REMOVED),
aAccessible, aIsFromUserInput, eAllowDupes)
, mStart(aStart)
, mIsInserted(aIsInserted)
, mModifiedText(aModifiedText)
{
// XXX We should use IsFromUserInput here, but that isn't always correct
// when the text change isn't related to content insertion or removal.
mIsFromUserInput = mAccessible->State() &
(states::FOCUSED | states::EDITABLE);
}
////////////////////////////////////////////////////////////////////////////////
// AccHideEvent
////////////////////////////////////////////////////////////////////////////////
AccHideEvent::
AccHideEvent(Accessible* aTarget, bool aNeedsShutdown) :
AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget),
mNeedsShutdown(aNeedsShutdown)
{
mNextSibling = mAccessible->NextSibling();
mPrevSibling = mAccessible->PrevSibling();
}
////////////////////////////////////////////////////////////////////////////////
// AccShowEvent
////////////////////////////////////////////////////////////////////////////////
AccShowEvent::
AccShowEvent(Accessible* aTarget) :
AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget)
{
int32_t idx = aTarget->IndexInParent();
MOZ_ASSERT(idx >= 0);
mInsertionIndex = idx;
}
////////////////////////////////////////////////////////////////////////////////
// AccTextSelChangeEvent
////////////////////////////////////////////////////////////////////////////////
AccTextSelChangeEvent::AccTextSelChangeEvent(HyperTextAccessible* aTarget,
dom::Selection* aSelection,
int32_t aReason) :
AccEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, aTarget,
eAutoDetect, eCoalesceTextSelChange),
mSel(aSelection), mReason(aReason) {}
AccTextSelChangeEvent::~AccTextSelChangeEvent() { }
bool
AccTextSelChangeEvent::IsCaretMoveOnly() const
{
return mSel->RangeCount() == 1 && mSel->IsCollapsed() &&
((mReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
nsISelectionListener::COLLAPSETOEND_REASON)) == 0);
}
////////////////////////////////////////////////////////////////////////////////
// AccSelChangeEvent
////////////////////////////////////////////////////////////////////////////////
AccSelChangeEvent::
AccSelChangeEvent(Accessible* aWidget, Accessible* aItem,
SelChangeType aSelChangeType) :
AccEvent(0, aItem, eAutoDetect, eCoalesceSelectionChange),
mWidget(aWidget), mItem(aItem), mSelChangeType(aSelChangeType),
mPreceedingCount(0), mPackedEvent(nullptr)
{
if (aSelChangeType == eSelectionAdd) {
if (mWidget->GetSelectedItem(1))
mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
else
mEventType = nsIAccessibleEvent::EVENT_SELECTION;
} else {
mEventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
}
}
////////////////////////////////////////////////////////////////////////////////
// AccTableChangeEvent
////////////////////////////////////////////////////////////////////////////////
AccTableChangeEvent::
AccTableChangeEvent(Accessible* aAccessible, uint32_t aEventType,
int32_t aRowOrColIndex, int32_t aNumRowsOrCols) :
AccEvent(aEventType, aAccessible),
mRowOrColIndex(aRowOrColIndex), mNumRowsOrCols(aNumRowsOrCols)
{
}
////////////////////////////////////////////////////////////////////////////////
// AccVCChangeEvent
////////////////////////////////////////////////////////////////////////////////
AccVCChangeEvent::
AccVCChangeEvent(Accessible* aAccessible,
Accessible* aOldAccessible,
int32_t aOldStart, int32_t aOldEnd,
int16_t aReason, EIsFromUserInput aIsFromUserInput) :
AccEvent(::nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED, aAccessible,
aIsFromUserInput),
mOldAccessible(aOldAccessible), mOldStart(aOldStart), mOldEnd(aOldEnd),
mReason(aReason)
{
}
already_AddRefed<nsIAccessibleEvent>
a11y::MakeXPCEvent(AccEvent* aEvent)
{
DocAccessible* doc = aEvent->Document();
Accessible* acc = aEvent->GetAccessible();
nsINode* node = acc->GetNode();
nsIDOMNode* domNode = node ? node->AsDOMNode() : nullptr;
bool fromUser = aEvent->IsFromUserInput();
uint32_t type = aEvent->GetEventType();
uint32_t eventGroup = aEvent->GetEventGroups();
nsCOMPtr<nsIAccessibleEvent> xpEvent;
if (eventGroup & (1 << AccEvent::eStateChangeEvent)) {
AccStateChangeEvent* sc = downcast_accEvent(aEvent);
bool extra = false;
uint32_t state = nsAccUtils::To32States(sc->GetState(), &extra);
xpEvent = new xpcAccStateChangeEvent(type, ToXPC(acc), ToXPCDocument(doc),
domNode, fromUser,
state, extra, sc->IsStateEnabled());
return xpEvent.forget();
}
if (eventGroup & (1 << AccEvent::eTextChangeEvent)) {
AccTextChangeEvent* tc = downcast_accEvent(aEvent);
nsString text;
tc->GetModifiedText(text);
xpEvent = new xpcAccTextChangeEvent(type, ToXPC(acc), ToXPCDocument(doc),
domNode, fromUser,
tc->GetStartOffset(), tc->GetLength(),
tc->IsTextInserted(), text);
return xpEvent.forget();
}
if (eventGroup & (1 << AccEvent::eHideEvent)) {
AccHideEvent* hideEvent = downcast_accEvent(aEvent);
xpEvent = new xpcAccHideEvent(type, ToXPC(acc), ToXPCDocument(doc),
domNode, fromUser,
ToXPC(hideEvent->TargetParent()),
ToXPC(hideEvent->TargetNextSibling()),
ToXPC(hideEvent->TargetPrevSibling()));
return xpEvent.forget();
}
if (eventGroup & (1 << AccEvent::eCaretMoveEvent)) {
AccCaretMoveEvent* cm = downcast_accEvent(aEvent);
xpEvent = new xpcAccCaretMoveEvent(type, ToXPC(acc), ToXPCDocument(doc),
domNode, fromUser,
cm->GetCaretOffset());
return xpEvent.forget();
}
if (eventGroup & (1 << AccEvent::eVirtualCursorChangeEvent)) {
AccVCChangeEvent* vcc = downcast_accEvent(aEvent);
xpEvent = new xpcAccVirtualCursorChangeEvent(type,
ToXPC(acc), ToXPCDocument(doc),
domNode, fromUser,
ToXPC(vcc->OldAccessible()),
vcc->OldStartOffset(),
vcc->OldEndOffset(),
vcc->Reason());
return xpEvent.forget();
}
if (eventGroup & (1 << AccEvent::eObjectAttrChangedEvent)) {
AccObjectAttrChangedEvent* oac = downcast_accEvent(aEvent);
xpEvent = new xpcAccObjectAttributeChangedEvent(type,
ToXPC(acc),
ToXPCDocument(doc), domNode,
fromUser,
oac->GetAttribute());
return xpEvent.forget();
}
xpEvent = new xpcAccEvent(type, ToXPC(acc), ToXPCDocument(doc), domNode, fromUser);
return xpEvent.forget();
}

570
accessible/base/AccEvent.h Normal file
View file

@ -0,0 +1,570 @@
/* -*- 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/. */
#ifndef _AccEvent_H_
#define _AccEvent_H_
#include "nsIAccessibleEvent.h"
#include "mozilla/a11y/Accessible.h"
class nsEventShell;
namespace mozilla {
namespace dom {
class Selection;
}
namespace a11y {
class DocAccessible;
// Constants used to point whether the event is from user input.
enum EIsFromUserInput
{
// eNoUserInput: event is not from user input
eNoUserInput = 0,
// eFromUserInput: event is from user input
eFromUserInput = 1,
// eAutoDetect: the value should be obtained from event state manager
eAutoDetect = -1
};
/**
* Generic accessible event.
*/
class AccEvent
{
public:
// Rule for accessible events.
// The rule will be applied when flushing pending events.
enum EEventRule {
// eAllowDupes : More than one event of the same type is allowed.
// This event will always be emitted. This flag is used for events that
// don't support coalescence.
eAllowDupes,
// eCoalesceReorder : For reorder events from the same subtree or the same
// node, only the umbrella event on the ancestor will be emitted.
eCoalesceReorder,
// eCoalesceOfSameType : For events of the same type, only the newest event
// will be processed.
eCoalesceOfSameType,
// eCoalesceSelectionChange: coalescence of selection change events.
eCoalesceSelectionChange,
// eCoalesceStateChange: coalesce state change events.
eCoalesceStateChange,
// eCoalesceTextSelChange: coalescence of text selection change events.
eCoalesceTextSelChange,
// eRemoveDupes : For repeat events, only the newest event in queue
// will be emitted.
eRemoveDupes,
// eDoNotEmit : This event is confirmed as a duplicate, do not emit it.
eDoNotEmit
};
// Initialize with an accessible.
AccEvent(uint32_t aEventType, Accessible* aAccessible,
EIsFromUserInput aIsFromUserInput = eAutoDetect,
EEventRule aEventRule = eRemoveDupes);
// AccEvent
uint32_t GetEventType() const { return mEventType; }
EEventRule GetEventRule() const { return mEventRule; }
bool IsFromUserInput() const { return mIsFromUserInput; }
EIsFromUserInput FromUserInput() const
{ return static_cast<EIsFromUserInput>(mIsFromUserInput); }
Accessible* GetAccessible() const { return mAccessible; }
DocAccessible* Document() const { return mAccessible->Document(); }
/**
* Down casting.
*/
enum EventGroup {
eGenericEvent,
eStateChangeEvent,
eTextChangeEvent,
eTreeMutationEvent,
eMutationEvent,
eReorderEvent,
eHideEvent,
eShowEvent,
eCaretMoveEvent,
eTextSelChangeEvent,
eSelectionChangeEvent,
eTableChangeEvent,
eVirtualCursorChangeEvent,
eObjectAttrChangedEvent
};
static const EventGroup kEventGroup = eGenericEvent;
virtual unsigned int GetEventGroups() const
{
return 1U << eGenericEvent;
}
/**
* Reference counting and cycle collection.
*/
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AccEvent)
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AccEvent)
protected:
virtual ~AccEvent() {}
bool mIsFromUserInput;
uint32_t mEventType;
EEventRule mEventRule;
RefPtr<Accessible> mAccessible;
friend class EventQueue;
friend class EventTree;
friend class ::nsEventShell;
friend class NotificationController;
};
/**
* Accessible state change event.
*/
class AccStateChangeEvent: public AccEvent
{
public:
AccStateChangeEvent(Accessible* aAccessible, uint64_t aState,
bool aIsEnabled,
EIsFromUserInput aIsFromUserInput = eAutoDetect) :
AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible,
aIsFromUserInput, eCoalesceStateChange),
mState(aState), mIsEnabled(aIsEnabled) { }
AccStateChangeEvent(Accessible* aAccessible, uint64_t aState) :
AccEvent(::nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible,
eAutoDetect, eCoalesceStateChange), mState(aState)
{ mIsEnabled = (mAccessible->State() & mState) != 0; }
// AccEvent
static const EventGroup kEventGroup = eStateChangeEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eStateChangeEvent);
}
// AccStateChangeEvent
uint64_t GetState() const { return mState; }
bool IsStateEnabled() const { return mIsEnabled; }
private:
uint64_t mState;
bool mIsEnabled;
friend class EventQueue;
};
/**
* Accessible text change event.
*/
class AccTextChangeEvent: public AccEvent
{
public:
AccTextChangeEvent(Accessible* aAccessible, int32_t aStart,
const nsAString& aModifiedText, bool aIsInserted,
EIsFromUserInput aIsFromUserInput = eAutoDetect);
// AccEvent
static const EventGroup kEventGroup = eTextChangeEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eTextChangeEvent);
}
// AccTextChangeEvent
int32_t GetStartOffset() const { return mStart; }
uint32_t GetLength() const { return mModifiedText.Length(); }
bool IsTextInserted() const { return mIsInserted; }
void GetModifiedText(nsAString& aModifiedText)
{ aModifiedText = mModifiedText; }
const nsString& ModifiedText() const { return mModifiedText; }
private:
int32_t mStart;
bool mIsInserted;
nsString mModifiedText;
friend class EventTree;
friend class NotificationController;
};
/**
* A base class for events related to tree mutation, either an AccMutation
* event, or an AccReorderEvent.
*/
class AccTreeMutationEvent : public AccEvent
{
public:
AccTreeMutationEvent(uint32_t aEventType, Accessible* aTarget) :
AccEvent(aEventType, aTarget, eAutoDetect, eCoalesceReorder), mGeneration(0) {}
// Event
static const EventGroup kEventGroup = eTreeMutationEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eTreeMutationEvent);
}
void SetNextEvent(AccTreeMutationEvent* aNext) { mNextEvent = aNext; }
void SetPrevEvent(AccTreeMutationEvent* aPrev) { mPrevEvent = aPrev; }
AccTreeMutationEvent* NextEvent() const { return mNextEvent; }
AccTreeMutationEvent* PrevEvent() const { return mPrevEvent; }
/**
* A sequence number to know when this event was fired.
*/
uint32_t EventGeneration() const { return mGeneration; }
void SetEventGeneration(uint32_t aGeneration) { mGeneration = aGeneration; }
private:
RefPtr<AccTreeMutationEvent> mNextEvent;
RefPtr<AccTreeMutationEvent> mPrevEvent;
uint32_t mGeneration;
};
/**
* Base class for show and hide accessible events.
*/
class AccMutationEvent: public AccTreeMutationEvent
{
public:
AccMutationEvent(uint32_t aEventType, Accessible* aTarget) :
AccTreeMutationEvent(aEventType, aTarget)
{
// Don't coalesce these since they are coalesced by reorder event. Coalesce
// contained text change events.
mParent = mAccessible->Parent();
}
virtual ~AccMutationEvent() { }
// Event
static const EventGroup kEventGroup = eMutationEvent;
virtual unsigned int GetEventGroups() const override
{
return AccTreeMutationEvent::GetEventGroups() | (1U << eMutationEvent);
}
// MutationEvent
bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; }
bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; }
Accessible* Parent() const { return mParent; }
protected:
nsCOMPtr<nsINode> mNode;
RefPtr<Accessible> mParent;
RefPtr<AccTextChangeEvent> mTextChangeEvent;
friend class EventTree;
friend class NotificationController;
};
/**
* Accessible hide event.
*/
class AccHideEvent: public AccMutationEvent
{
public:
explicit AccHideEvent(Accessible* aTarget, bool aNeedsShutdown = true);
// Event
static const EventGroup kEventGroup = eHideEvent;
virtual unsigned int GetEventGroups() const override
{
return AccMutationEvent::GetEventGroups() | (1U << eHideEvent);
}
// AccHideEvent
Accessible* TargetParent() const { return mParent; }
Accessible* TargetNextSibling() const { return mNextSibling; }
Accessible* TargetPrevSibling() const { return mPrevSibling; }
bool NeedsShutdown() const { return mNeedsShutdown; }
protected:
bool mNeedsShutdown;
RefPtr<Accessible> mNextSibling;
RefPtr<Accessible> mPrevSibling;
friend class EventTree;
friend class NotificationController;
};
/**
* Accessible show event.
*/
class AccShowEvent: public AccMutationEvent
{
public:
explicit AccShowEvent(Accessible* aTarget);
// Event
static const EventGroup kEventGroup = eShowEvent;
virtual unsigned int GetEventGroups() const override
{
return AccMutationEvent::GetEventGroups() | (1U << eShowEvent);
}
uint32_t InsertionIndex() const { return mInsertionIndex; }
private:
nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents;
uint32_t mInsertionIndex;
friend class EventTree;
};
/**
* Class for reorder accessible event. Takes care about
*/
class AccReorderEvent : public AccTreeMutationEvent
{
public:
explicit AccReorderEvent(Accessible* aTarget) :
AccTreeMutationEvent(::nsIAccessibleEvent::EVENT_REORDER, aTarget) { }
virtual ~AccReorderEvent() { }
// Event
static const EventGroup kEventGroup = eReorderEvent;
virtual unsigned int GetEventGroups() const override
{
return AccTreeMutationEvent::GetEventGroups() | (1U << eReorderEvent);
}
};
/**
* Accessible caret move event.
*/
class AccCaretMoveEvent: public AccEvent
{
public:
AccCaretMoveEvent(Accessible* aAccessible, int32_t aCaretOffset,
EIsFromUserInput aIsFromUserInput = eAutoDetect) :
AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible,
aIsFromUserInput),
mCaretOffset(aCaretOffset) { }
virtual ~AccCaretMoveEvent() { }
// AccEvent
static const EventGroup kEventGroup = eCaretMoveEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eCaretMoveEvent);
}
// AccCaretMoveEvent
int32_t GetCaretOffset() const { return mCaretOffset; }
private:
int32_t mCaretOffset;
};
/**
* Accessible text selection change event.
*/
class AccTextSelChangeEvent : public AccEvent
{
public:
AccTextSelChangeEvent(HyperTextAccessible* aTarget,
dom::Selection* aSelection,
int32_t aReason);
virtual ~AccTextSelChangeEvent();
// AccEvent
static const EventGroup kEventGroup = eTextSelChangeEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eTextSelChangeEvent);
}
// AccTextSelChangeEvent
/**
* Return true if the text selection change wasn't caused by pure caret move.
*/
bool IsCaretMoveOnly() const;
private:
RefPtr<dom::Selection> mSel;
int32_t mReason;
friend class EventQueue;
friend class SelectionManager;
};
/**
* Accessible widget selection change event.
*/
class AccSelChangeEvent : public AccEvent
{
public:
enum SelChangeType {
eSelectionAdd,
eSelectionRemove
};
AccSelChangeEvent(Accessible* aWidget, Accessible* aItem,
SelChangeType aSelChangeType);
virtual ~AccSelChangeEvent() { }
// AccEvent
static const EventGroup kEventGroup = eSelectionChangeEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent);
}
// AccSelChangeEvent
Accessible* Widget() const { return mWidget; }
private:
RefPtr<Accessible> mWidget;
RefPtr<Accessible> mItem;
SelChangeType mSelChangeType;
uint32_t mPreceedingCount;
AccSelChangeEvent* mPackedEvent;
friend class EventQueue;
};
/**
* Accessible table change event.
*/
class AccTableChangeEvent : public AccEvent
{
public:
AccTableChangeEvent(Accessible* aAccessible, uint32_t aEventType,
int32_t aRowOrColIndex, int32_t aNumRowsOrCols);
// AccEvent
static const EventGroup kEventGroup = eTableChangeEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eTableChangeEvent);
}
// AccTableChangeEvent
uint32_t GetIndex() const { return mRowOrColIndex; }
uint32_t GetCount() const { return mNumRowsOrCols; }
private:
uint32_t mRowOrColIndex; // the start row/column after which the rows are inserted/deleted.
uint32_t mNumRowsOrCols; // the number of inserted/deleted rows/columns
};
/**
* Accessible virtual cursor change event.
*/
class AccVCChangeEvent : public AccEvent
{
public:
AccVCChangeEvent(Accessible* aAccessible,
Accessible* aOldAccessible,
int32_t aOldStart, int32_t aOldEnd,
int16_t aReason,
EIsFromUserInput aIsFromUserInput = eFromUserInput);
virtual ~AccVCChangeEvent() { }
// AccEvent
static const EventGroup kEventGroup = eVirtualCursorChangeEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eVirtualCursorChangeEvent);
}
// AccTableChangeEvent
Accessible* OldAccessible() const { return mOldAccessible; }
int32_t OldStartOffset() const { return mOldStart; }
int32_t OldEndOffset() const { return mOldEnd; }
int32_t Reason() const { return mReason; }
private:
RefPtr<Accessible> mOldAccessible;
int32_t mOldStart;
int32_t mOldEnd;
int16_t mReason;
};
/**
* Accessible object attribute changed event.
*/
class AccObjectAttrChangedEvent: public AccEvent
{
public:
AccObjectAttrChangedEvent(Accessible* aAccessible, nsIAtom* aAttribute) :
AccEvent(::nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, aAccessible),
mAttribute(aAttribute) { }
// AccEvent
static const EventGroup kEventGroup = eObjectAttrChangedEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eObjectAttrChangedEvent);
}
// AccObjectAttrChangedEvent
nsIAtom* GetAttribute() const { return mAttribute; }
private:
nsCOMPtr<nsIAtom> mAttribute;
virtual ~AccObjectAttrChangedEvent() { }
};
/**
* Downcast the generic accessible event object to derived type.
*/
class downcast_accEvent
{
public:
explicit downcast_accEvent(AccEvent* e) : mRawPtr(e) { }
template<class Destination>
operator Destination*() {
if (!mRawPtr)
return nullptr;
return mRawPtr->GetEventGroups() & (1U << Destination::kEventGroup) ?
static_cast<Destination*>(mRawPtr) : nullptr;
}
private:
AccEvent* mRawPtr;
};
/**
* Return a new xpcom accessible event for the given internal one.
*/
already_AddRefed<nsIAccessibleEvent>
MakeXPCEvent(AccEvent* aEvent);
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,227 @@
/* 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 "AccGroupInfo.h"
#include "nsAccUtils.h"
#include "Role.h"
#include "States.h"
using namespace mozilla::a11y;
AccGroupInfo::AccGroupInfo(Accessible* aItem, role aRole) :
mPosInSet(0), mSetSize(0), mParent(nullptr), mItem(aItem), mRole(aRole)
{
MOZ_COUNT_CTOR(AccGroupInfo);
Update();
}
void
AccGroupInfo::Update()
{
Accessible* parent = mItem->Parent();
if (!parent)
return;
int32_t indexInParent = mItem->IndexInParent();
uint32_t siblingCount = parent->ChildCount();
if (indexInParent == -1 ||
indexInParent >= static_cast<int32_t>(siblingCount)) {
NS_ERROR("Wrong index in parent! Tree invalidation problem.");
return;
}
int32_t level = nsAccUtils::GetARIAOrDefaultLevel(mItem);
// Compute position in set.
mPosInSet = 1;
for (int32_t idx = indexInParent - 1; idx >= 0 ; idx--) {
Accessible* sibling = parent->GetChildAt(idx);
roles::Role siblingRole = sibling->Role();
// If the sibling is separator then the group is ended.
if (siblingRole == roles::SEPARATOR)
break;
// If sibling is not visible and hasn't the same base role.
if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE)
continue;
// Check if it's hierarchical flatten structure, i.e. if the sibling
// level is lesser than this one then group is ended, if the sibling level
// is greater than this one then the group is split by some child elements
// (group will be continued).
int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling);
if (siblingLevel < level) {
mParent = sibling;
break;
}
// Skip subset.
if (siblingLevel > level)
continue;
// If the previous item in the group has calculated group information then
// build group information for this item based on found one.
if (sibling->mBits.groupInfo) {
mPosInSet += sibling->mBits.groupInfo->mPosInSet;
mParent = sibling->mBits.groupInfo->mParent;
mSetSize = sibling->mBits.groupInfo->mSetSize;
return;
}
mPosInSet++;
}
// Compute set size.
mSetSize = mPosInSet;
for (uint32_t idx = indexInParent + 1; idx < siblingCount; idx++) {
Accessible* sibling = parent->GetChildAt(idx);
roles::Role siblingRole = sibling->Role();
// If the sibling is separator then the group is ended.
if (siblingRole == roles::SEPARATOR)
break;
// If sibling is visible and has the same base role
if (BaseRole(siblingRole) != mRole || sibling->State() & states::INVISIBLE)
continue;
// and check if it's hierarchical flatten structure.
int32_t siblingLevel = nsAccUtils::GetARIAOrDefaultLevel(sibling);
if (siblingLevel < level)
break;
// Skip subset.
if (siblingLevel > level)
continue;
// If the next item in the group has calculated group information then
// build group information for this item based on found one.
if (sibling->mBits.groupInfo) {
mParent = sibling->mBits.groupInfo->mParent;
mSetSize = sibling->mBits.groupInfo->mSetSize;
return;
}
mSetSize++;
}
if (mParent)
return;
roles::Role parentRole = parent->Role();
if (ShouldReportRelations(mRole, parentRole))
mParent = parent;
// ARIA tree and list can be arranged by using ARIA groups to organize levels.
if (parentRole != roles::GROUPING)
return;
// Way #1 for ARIA tree (not ARIA treegrid): previous sibling of a group is a
// parent. In other words the parent of the tree item will be a group and
// the previous tree item of the group is a conceptual parent of the tree
// item.
if (mRole == roles::OUTLINEITEM) {
Accessible* parentPrevSibling = parent->PrevSibling();
if (parentPrevSibling && parentPrevSibling->Role() == mRole) {
mParent = parentPrevSibling;
return;
}
}
// Way #2 for ARIA list and tree: group is a child of an item. In other words
// the parent of the item will be a group and containing item of the group is
// a conceptual parent of the item.
if (mRole == roles::LISTITEM || mRole == roles::OUTLINEITEM) {
Accessible* grandParent = parent->Parent();
if (grandParent && grandParent->Role() == mRole)
mParent = grandParent;
}
}
Accessible*
AccGroupInfo::FirstItemOf(Accessible* aContainer)
{
// ARIA tree can be arranged by ARIA groups case #1 (previous sibling of a
// group is a parent) or by aria-level.
a11y::role containerRole = aContainer->Role();
Accessible* item = aContainer->NextSibling();
if (item) {
if (containerRole == roles::OUTLINEITEM && item->Role() == roles::GROUPING)
item = item->FirstChild();
if (item) {
AccGroupInfo* itemGroupInfo = item->GetGroupInfo();
if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer)
return item;
}
}
// ARIA list and tree can be arranged by ARIA groups case #2 (group is
// a child of an item).
item = aContainer->LastChild();
if (!item)
return nullptr;
if (item->Role() == roles::GROUPING &&
(containerRole == roles::LISTITEM || containerRole == roles::OUTLINEITEM)) {
item = item->FirstChild();
if (item) {
AccGroupInfo* itemGroupInfo = item->GetGroupInfo();
if (itemGroupInfo && itemGroupInfo->ConceptualParent() == aContainer)
return item;
}
}
// Otherwise, it can be a direct child if the container is a list or tree.
item = aContainer->FirstChild();
if (ShouldReportRelations(item->Role(), containerRole))
return item;
return nullptr;
}
Accessible*
AccGroupInfo::NextItemTo(Accessible* aItem)
{
AccGroupInfo* groupInfo = aItem->GetGroupInfo();
if (!groupInfo)
return nullptr;
// If the item in middle of the group then search next item in siblings.
if (groupInfo->PosInSet() >= groupInfo->SetSize())
return nullptr;
Accessible* parent = aItem->Parent();
uint32_t childCount = parent->ChildCount();
for (uint32_t idx = aItem->IndexInParent() + 1; idx < childCount; idx++) {
Accessible* nextItem = parent->GetChildAt(idx);
AccGroupInfo* nextGroupInfo = nextItem->GetGroupInfo();
if (nextGroupInfo &&
nextGroupInfo->ConceptualParent() == groupInfo->ConceptualParent()) {
return nextItem;
}
}
NS_NOTREACHED("Item in the middle of the group but there's no next item!");
return nullptr;
}
bool
AccGroupInfo::ShouldReportRelations(role aRole, role aParentRole)
{
// We only want to report hierarchy-based node relations for items in tree or
// list form. ARIA level/owns relations are always reported.
if (aParentRole == roles::OUTLINE && aRole == roles::OUTLINEITEM)
return true;
if (aParentRole == roles::TREE_TABLE && aRole == roles::ROW)
return true;
if (aParentRole == roles::LIST && aRole == roles::LISTITEM)
return true;
return false;
}

View file

@ -0,0 +1,114 @@
/* 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/. */
#ifndef AccGroupInfo_h_
#define AccGroupInfo_h_
#include "Accessible-inl.h"
namespace mozilla {
namespace a11y {
/**
* Calculate and store group information.
*/
class AccGroupInfo
{
public:
~AccGroupInfo() { MOZ_COUNT_DTOR(AccGroupInfo); }
/**
* Return 1-based position in the group.
*/
uint32_t PosInSet() const { return mPosInSet; }
/**
* Return a number of items in the group.
*/
uint32_t SetSize() const { return mSetSize; }
/**
* Return a direct or logical parent of the accessible that this group info is
* created for.
*/
Accessible* ConceptualParent() const { return mParent; }
/**
* Update group information.
*/
void Update();
/**
* Create group info.
*/
static AccGroupInfo* CreateGroupInfo(Accessible* aAccessible)
{
mozilla::a11y::role role = aAccessible->Role();
if (role != mozilla::a11y::roles::ROW &&
role != mozilla::a11y::roles::OUTLINEITEM &&
role != mozilla::a11y::roles::OPTION &&
role != mozilla::a11y::roles::LISTITEM &&
role != mozilla::a11y::roles::MENUITEM &&
role != mozilla::a11y::roles::COMBOBOX_OPTION &&
role != mozilla::a11y::roles::RICH_OPTION &&
role != mozilla::a11y::roles::CHECK_RICH_OPTION &&
role != mozilla::a11y::roles::PARENT_MENUITEM &&
role != mozilla::a11y::roles::CHECK_MENU_ITEM &&
role != mozilla::a11y::roles::RADIO_MENU_ITEM &&
role != mozilla::a11y::roles::RADIOBUTTON &&
role != mozilla::a11y::roles::PAGETAB)
return nullptr;
AccGroupInfo* info = new AccGroupInfo(aAccessible, BaseRole(role));
return info;
}
/**
* Return a first item for the given container.
*/
static Accessible* FirstItemOf(Accessible* aContainer);
/**
* Return next item of the same group to the given item.
*/
static Accessible* NextItemTo(Accessible* aItem);
protected:
AccGroupInfo(Accessible* aItem, a11y::role aRole);
private:
AccGroupInfo() = delete;
AccGroupInfo(const AccGroupInfo&) = delete;
AccGroupInfo& operator =(const AccGroupInfo&) = delete;
static mozilla::a11y::role BaseRole(mozilla::a11y::role aRole)
{
if (aRole == mozilla::a11y::roles::CHECK_MENU_ITEM ||
aRole == mozilla::a11y::roles::PARENT_MENUITEM ||
aRole == mozilla::a11y::roles::RADIO_MENU_ITEM)
return mozilla::a11y::roles::MENUITEM;
if (aRole == mozilla::a11y::roles::CHECK_RICH_OPTION)
return mozilla::a11y::roles::RICH_OPTION;
return aRole;
}
/**
* Return true if the given parent and child roles should have their node
* relations reported.
*/
static bool ShouldReportRelations(a11y::role aRole, a11y::role aParentRole);
uint32_t mPosInSet;
uint32_t mSetSize;
Accessible* mParent;
Accessible* mItem;
a11y::role mRole;
};
} // namespace mozilla
} // namespace a11y
#endif

View file

@ -0,0 +1,414 @@
/* 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 "AccIterator.h"
#include "AccGroupInfo.h"
#ifdef MOZ_XUL
#include "XULTreeAccessible.h"
#endif
#include "mozilla/dom/HTMLLabelElement.h"
using namespace mozilla;
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// AccIterator
////////////////////////////////////////////////////////////////////////////////
AccIterator::AccIterator(Accessible* aAccessible,
filters::FilterFuncPtr aFilterFunc) :
mFilterFunc(aFilterFunc)
{
mState = new IteratorState(aAccessible);
}
AccIterator::~AccIterator()
{
while (mState) {
IteratorState *tmp = mState;
mState = tmp->mParentState;
delete tmp;
}
}
Accessible*
AccIterator::Next()
{
while (mState) {
Accessible* child = mState->mParent->GetChildAt(mState->mIndex++);
if (!child) {
IteratorState* tmp = mState;
mState = mState->mParentState;
delete tmp;
continue;
}
uint32_t result = mFilterFunc(child);
if (result & filters::eMatch)
return child;
if (!(result & filters::eSkipSubtree)) {
IteratorState* childState = new IteratorState(child, mState);
mState = childState;
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// nsAccIterator::IteratorState
AccIterator::IteratorState::IteratorState(Accessible* aParent,
IteratorState *mParentState) :
mParent(aParent), mIndex(0), mParentState(mParentState)
{
}
////////////////////////////////////////////////////////////////////////////////
// RelatedAccIterator
////////////////////////////////////////////////////////////////////////////////
RelatedAccIterator::
RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
nsIAtom* aRelAttr) :
mDocument(aDocument), mRelAttr(aRelAttr), mProviders(nullptr),
mBindingParent(nullptr), mIndex(0)
{
mBindingParent = aDependentContent->GetBindingParent();
nsIAtom* IDAttr = mBindingParent ?
nsGkAtoms::anonid : nsGkAtoms::id;
nsAutoString id;
if (aDependentContent->GetAttr(kNameSpaceID_None, IDAttr, id))
mProviders = mDocument->mDependentIDsHash.Get(id);
}
Accessible*
RelatedAccIterator::Next()
{
if (!mProviders)
return nullptr;
while (mIndex < mProviders->Length()) {
DocAccessible::AttrRelProvider* provider = (*mProviders)[mIndex++];
// Return related accessible for the given attribute and if the provider
// content is in the same binding in the case of XBL usage.
if (provider->mRelAttr == mRelAttr) {
nsIContent* bindingParent = provider->mContent->GetBindingParent();
bool inScope = mBindingParent == bindingParent ||
mBindingParent == provider->mContent;
if (inScope) {
Accessible* related = mDocument->GetAccessible(provider->mContent);
if (related)
return related;
// If the document content is pointed by relation then return the document
// itself.
if (provider->mContent == mDocument->GetContent())
return mDocument;
}
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// HTMLLabelIterator
////////////////////////////////////////////////////////////////////////////////
HTMLLabelIterator::
HTMLLabelIterator(DocAccessible* aDocument, const Accessible* aAccessible,
LabelFilter aFilter) :
mRelIter(aDocument, aAccessible->GetContent(), nsGkAtoms::_for),
mAcc(aAccessible), mLabelFilter(aFilter)
{
}
bool
HTMLLabelIterator::IsLabel(Accessible* aLabel)
{
dom::HTMLLabelElement* labelEl =
dom::HTMLLabelElement::FromContent(aLabel->GetContent());
return labelEl && labelEl->GetControl() == mAcc->GetContent();
}
Accessible*
HTMLLabelIterator::Next()
{
// Get either <label for="[id]"> element which explicitly points to given
// element, or <label> ancestor which implicitly point to it.
Accessible* label = nullptr;
while ((label = mRelIter.Next())) {
if (IsLabel(label)) {
return label;
}
}
// Ignore ancestor label on not widget accessible.
if (mLabelFilter == eSkipAncestorLabel || !mAcc->IsWidget())
return nullptr;
// Go up tree to get a name of ancestor label if there is one (an ancestor
// <label> implicitly points to us). Don't go up farther than form or
// document.
Accessible* walkUp = mAcc->Parent();
while (walkUp && !walkUp->IsDoc()) {
nsIContent* walkUpEl = walkUp->GetContent();
if (IsLabel(walkUp) &&
!walkUpEl->HasAttr(kNameSpaceID_None, nsGkAtoms::_for)) {
mLabelFilter = eSkipAncestorLabel; // prevent infinite loop
return walkUp;
}
if (walkUpEl->IsHTMLElement(nsGkAtoms::form))
break;
walkUp = walkUp->Parent();
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// HTMLOutputIterator
////////////////////////////////////////////////////////////////////////////////
HTMLOutputIterator::
HTMLOutputIterator(DocAccessible* aDocument, nsIContent* aElement) :
mRelIter(aDocument, aElement, nsGkAtoms::_for)
{
}
Accessible*
HTMLOutputIterator::Next()
{
Accessible* output = nullptr;
while ((output = mRelIter.Next())) {
if (output->GetContent()->IsHTMLElement(nsGkAtoms::output))
return output;
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// XULLabelIterator
////////////////////////////////////////////////////////////////////////////////
XULLabelIterator::
XULLabelIterator(DocAccessible* aDocument, nsIContent* aElement) :
mRelIter(aDocument, aElement, nsGkAtoms::control)
{
}
Accessible*
XULLabelIterator::Next()
{
Accessible* label = nullptr;
while ((label = mRelIter.Next())) {
if (label->GetContent()->IsXULElement(nsGkAtoms::label))
return label;
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// XULDescriptionIterator
////////////////////////////////////////////////////////////////////////////////
XULDescriptionIterator::
XULDescriptionIterator(DocAccessible* aDocument, nsIContent* aElement) :
mRelIter(aDocument, aElement, nsGkAtoms::control)
{
}
Accessible*
XULDescriptionIterator::Next()
{
Accessible* descr = nullptr;
while ((descr = mRelIter.Next())) {
if (descr->GetContent()->IsXULElement(nsGkAtoms::description))
return descr;
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// IDRefsIterator
////////////////////////////////////////////////////////////////////////////////
IDRefsIterator::
IDRefsIterator(DocAccessible* aDoc, nsIContent* aContent,
nsIAtom* aIDRefsAttr) :
mContent(aContent), mDoc(aDoc), mCurrIdx(0)
{
if (mContent->IsInUncomposedDoc())
mContent->GetAttr(kNameSpaceID_None, aIDRefsAttr, mIDs);
}
const nsDependentSubstring
IDRefsIterator::NextID()
{
for (; mCurrIdx < mIDs.Length(); mCurrIdx++) {
if (!NS_IsAsciiWhitespace(mIDs[mCurrIdx]))
break;
}
if (mCurrIdx >= mIDs.Length())
return nsDependentSubstring();
nsAString::index_type idStartIdx = mCurrIdx;
while (++mCurrIdx < mIDs.Length()) {
if (NS_IsAsciiWhitespace(mIDs[mCurrIdx]))
break;
}
return Substring(mIDs, idStartIdx, mCurrIdx++ - idStartIdx);
}
nsIContent*
IDRefsIterator::NextElem()
{
while (true) {
const nsDependentSubstring id = NextID();
if (id.IsEmpty())
break;
nsIContent* refContent = GetElem(id);
if (refContent)
return refContent;
}
return nullptr;
}
nsIContent*
IDRefsIterator::GetElem(const nsDependentSubstring& aID)
{
// Get elements in DOM tree by ID attribute if this is an explicit content.
// In case of bound element check its anonymous subtree.
if (!mContent->IsInAnonymousSubtree()) {
dom::Element* refElm = mContent->OwnerDoc()->GetElementById(aID);
if (refElm || !mContent->GetXBLBinding())
return refElm;
}
// If content is in anonymous subtree or an element having anonymous subtree
// then use "anonid" attribute to get elements in anonymous subtree.
// Check inside the binding the element is contained in.
nsIContent* bindingParent = mContent->GetBindingParent();
if (bindingParent) {
nsIContent* refElm = bindingParent->OwnerDoc()->
GetAnonymousElementByAttribute(bindingParent, nsGkAtoms::anonid, aID);
if (refElm)
return refElm;
}
// Check inside the binding of the element.
if (mContent->GetXBLBinding()) {
return mContent->OwnerDoc()->
GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid, aID);
}
return nullptr;
}
Accessible*
IDRefsIterator::Next()
{
nsIContent* nextEl = nullptr;
while ((nextEl = NextElem())) {
Accessible* acc = mDoc->GetAccessible(nextEl);
if (acc) {
return acc;
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// SingleAccIterator
////////////////////////////////////////////////////////////////////////////////
Accessible*
SingleAccIterator::Next()
{
RefPtr<Accessible> nextAcc;
mAcc.swap(nextAcc);
if (!nextAcc || nextAcc->IsDefunct()) {
return nullptr;
}
return nextAcc;
}
////////////////////////////////////////////////////////////////////////////////
// ItemIterator
////////////////////////////////////////////////////////////////////////////////
Accessible*
ItemIterator::Next()
{
if (mContainer) {
mAnchor = AccGroupInfo::FirstItemOf(mContainer);
mContainer = nullptr;
return mAnchor;
}
return mAnchor ? (mAnchor = AccGroupInfo::NextItemTo(mAnchor)) : nullptr;
}
////////////////////////////////////////////////////////////////////////////////
// XULTreeItemIterator
////////////////////////////////////////////////////////////////////////////////
XULTreeItemIterator::XULTreeItemIterator(XULTreeAccessible* aXULTree,
nsITreeView* aTreeView,
int32_t aRowIdx) :
mXULTree(aXULTree), mTreeView(aTreeView), mRowCount(-1),
mContainerLevel(-1), mCurrRowIdx(aRowIdx + 1)
{
mTreeView->GetRowCount(&mRowCount);
if (aRowIdx != -1)
mTreeView->GetLevel(aRowIdx, &mContainerLevel);
}
Accessible*
XULTreeItemIterator::Next()
{
while (mCurrRowIdx < mRowCount) {
int32_t level = 0;
mTreeView->GetLevel(mCurrRowIdx, &level);
if (level == mContainerLevel + 1)
return mXULTree->GetTreeItemAccessible(mCurrRowIdx++);
if (level <= mContainerLevel) { // got level up
mCurrRowIdx = mRowCount;
break;
}
mCurrRowIdx++;
}
return nullptr;
}

View file

@ -0,0 +1,325 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_AccIterator_h__
#define mozilla_a11y_AccIterator_h__
#include "DocAccessible.h"
#include "Filters.h"
#include <memory>
class nsITreeView;
namespace mozilla {
namespace a11y {
/**
* AccIterable is a basic interface for iterators over accessibles.
*/
class AccIterable
{
public:
virtual ~AccIterable() { }
virtual Accessible* Next() = 0;
private:
friend class Relation;
std::unique_ptr<AccIterable> mNextIter;
};
/**
* Allows to iterate through accessible children or subtree complying with
* filter function.
*/
class AccIterator : public AccIterable
{
public:
AccIterator(Accessible* aRoot, filters::FilterFuncPtr aFilterFunc);
virtual ~AccIterator();
/**
* Return next accessible complying with filter function. Return the first
* accessible for the first time.
*/
virtual Accessible* Next() override;
private:
AccIterator();
AccIterator(const AccIterator&);
AccIterator& operator =(const AccIterator&);
struct IteratorState
{
explicit IteratorState(Accessible* aParent, IteratorState* mParentState = nullptr);
Accessible* mParent;
int32_t mIndex;
IteratorState* mParentState;
};
filters::FilterFuncPtr mFilterFunc;
IteratorState* mState;
};
/**
* Allows to traverse through related accessibles that are pointing to the given
* dependent accessible by relation attribute.
*/
class RelatedAccIterator : public AccIterable
{
public:
/**
* Constructor.
*
* @param aDocument [in] the document accessible the related
* & accessibles belong to.
* @param aDependentContent [in] the content of dependent accessible that
* relations were requested for
* @param aRelAttr [in] relation attribute that relations are
* pointed by
*/
RelatedAccIterator(DocAccessible* aDocument, nsIContent* aDependentContent,
nsIAtom* aRelAttr);
virtual ~RelatedAccIterator() { }
/**
* Return next related accessible for the given dependent accessible.
*/
virtual Accessible* Next() override;
private:
RelatedAccIterator();
RelatedAccIterator(const RelatedAccIterator&);
RelatedAccIterator& operator = (const RelatedAccIterator&);
DocAccessible* mDocument;
nsIAtom* mRelAttr;
DocAccessible::AttrRelProviderArray* mProviders;
nsIContent* mBindingParent;
uint32_t mIndex;
};
/**
* Used to iterate through HTML labels associated with the given accessible.
*/
class HTMLLabelIterator : public AccIterable
{
public:
enum LabelFilter {
eAllLabels,
eSkipAncestorLabel
};
HTMLLabelIterator(DocAccessible* aDocument, const Accessible* aAccessible,
LabelFilter aFilter = eAllLabels);
virtual ~HTMLLabelIterator() { }
/**
* Return next label accessible associated with the given element.
*/
virtual Accessible* Next() override;
private:
HTMLLabelIterator();
HTMLLabelIterator(const HTMLLabelIterator&);
HTMLLabelIterator& operator = (const HTMLLabelIterator&);
bool IsLabel(Accessible* aLabel);
RelatedAccIterator mRelIter;
// XXX: replace it on weak reference (bug 678429), it's safe to use raw
// pointer now because iterators life cycle is short.
const Accessible* mAcc;
LabelFilter mLabelFilter;
};
/**
* Used to iterate through HTML outputs associated with the given element.
*/
class HTMLOutputIterator : public AccIterable
{
public:
HTMLOutputIterator(DocAccessible* aDocument, nsIContent* aElement);
virtual ~HTMLOutputIterator() { }
/**
* Return next output accessible associated with the given element.
*/
virtual Accessible* Next() override;
private:
HTMLOutputIterator();
HTMLOutputIterator(const HTMLOutputIterator&);
HTMLOutputIterator& operator = (const HTMLOutputIterator&);
RelatedAccIterator mRelIter;
};
/**
* Used to iterate through XUL labels associated with the given element.
*/
class XULLabelIterator : public AccIterable
{
public:
XULLabelIterator(DocAccessible* aDocument, nsIContent* aElement);
virtual ~XULLabelIterator() { }
/**
* Return next label accessible associated with the given element.
*/
virtual Accessible* Next() override;
private:
XULLabelIterator();
XULLabelIterator(const XULLabelIterator&);
XULLabelIterator& operator = (const XULLabelIterator&);
RelatedAccIterator mRelIter;
};
/**
* Used to iterate through XUL descriptions associated with the given element.
*/
class XULDescriptionIterator : public AccIterable
{
public:
XULDescriptionIterator(DocAccessible* aDocument, nsIContent* aElement);
virtual ~XULDescriptionIterator() { }
/**
* Return next description accessible associated with the given element.
*/
virtual Accessible* Next() override;
private:
XULDescriptionIterator();
XULDescriptionIterator(const XULDescriptionIterator&);
XULDescriptionIterator& operator = (const XULDescriptionIterator&);
RelatedAccIterator mRelIter;
};
/**
* Used to iterate through IDs, elements or accessibles pointed by IDRefs
* attribute. Note, any method used to iterate through IDs, elements, or
* accessibles moves iterator to next position.
*/
class IDRefsIterator : public AccIterable
{
public:
IDRefsIterator(DocAccessible* aDoc, nsIContent* aContent,
nsIAtom* aIDRefsAttr);
virtual ~IDRefsIterator() { }
/**
* Return next ID.
*/
const nsDependentSubstring NextID();
/**
* Return next element.
*/
nsIContent* NextElem();
/**
* Return the element with the given ID.
*/
nsIContent* GetElem(const nsDependentSubstring& aID);
// AccIterable
virtual Accessible* Next() override;
private:
IDRefsIterator();
IDRefsIterator(const IDRefsIterator&);
IDRefsIterator operator = (const IDRefsIterator&);
nsString mIDs;
nsIContent* mContent;
DocAccessible* mDoc;
nsAString::index_type mCurrIdx;
};
/**
* Iterator that points to a single accessible returning it on the first call
* to Next().
*/
class SingleAccIterator : public AccIterable
{
public:
explicit SingleAccIterator(Accessible* aTarget): mAcc(aTarget) { }
virtual ~SingleAccIterator() { }
virtual Accessible* Next() override;
private:
SingleAccIterator();
SingleAccIterator(const SingleAccIterator&);
SingleAccIterator& operator = (const SingleAccIterator&);
RefPtr<Accessible> mAcc;
};
/**
* Used to iterate items of the given item container.
*/
class ItemIterator : public AccIterable
{
public:
explicit ItemIterator(Accessible* aItemContainer) :
mContainer(aItemContainer), mAnchor(nullptr) { }
virtual ~ItemIterator() { }
virtual Accessible* Next() override;
private:
ItemIterator() = delete;
ItemIterator(const ItemIterator&) = delete;
ItemIterator& operator = (const ItemIterator&) = delete;
Accessible* mContainer;
Accessible* mAnchor;
};
/**
* Used to iterate through XUL tree items of the same level.
*/
class XULTreeItemIterator : public AccIterable
{
public:
XULTreeItemIterator(XULTreeAccessible* aXULTree, nsITreeView* aTreeView,
int32_t aRowIdx);
virtual ~XULTreeItemIterator() { }
virtual Accessible* Next() override;
private:
XULTreeItemIterator() = delete;
XULTreeItemIterator(const XULTreeItemIterator&) = delete;
XULTreeItemIterator& operator = (const XULTreeItemIterator&) = delete;
XULTreeAccessible* mXULTree;
nsITreeView* mTreeView;
int32_t mRowCount;
int32_t mContainerLevel;
int32_t mCurrRowIdx;
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,94 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_AccTypes_h
#define mozilla_a11y_AccTypes_h
namespace mozilla {
namespace a11y {
/**
* Accessible object types. Each accessible class can have own type.
*/
enum AccType {
/**
* This set of types is used for accessible creation, keep them together in
* alphabetical order since they are used in switch statement.
*/
eNoType,
eHTMLBRType,
eHTMLButtonType,
eHTMLCanvasType,
eHTMLCaptionType,
eHTMLCheckboxType,
eHTMLComboboxType,
eHTMLFileInputType,
eHTMLGroupboxType,
eHTMLHRType,
eHTMLImageMapType,
eHTMLLiType,
eHTMLSelectListType,
eHTMLMediaType,
eHTMLRadioButtonType,
eHTMLRangeType,
eHTMLSpinnerType,
eHTMLTableType,
eHTMLTableCellType,
eHTMLTableRowType,
eHTMLTextFieldType,
eHyperTextType,
eImageType,
eOuterDocType,
ePluginType,
eTextLeafType,
/**
* Other accessible types.
*/
eApplicationType,
eHTMLOptGroupType,
eImageMapType,
eMenuPopupType,
eProxyType,
eProgressType,
eRootType,
eXULLabelType,
eXULListItemType,
eXULTabpanelsType,
eXULTreeType,
eLastAccType = eXULTreeType
};
/**
* Generic accessible type, different accessible classes can share the same
* type, the same accessible class can have several types.
*/
enum AccGenericType {
eAlert = 1 << 0,
eAutoComplete = 1 << 1,
eAutoCompletePopup = 1 << 2,
eButton = 1 << 3,
eCombobox = 1 << 4,
eDocument = 1 << 5,
eHyperText = 1 << 6,
eLandmark = 1 << 7,
eList = 1 << 8,
eListControl = 1 << 9,
eMenuButton = 1 << 10,
eSelect = 1 << 11,
eTable = 1 << 12,
eTableCell = 1 << 13,
eTableRow = 1 << 14,
eText = 1 << 15,
eLastAccGenericType = eText
};
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11y_AccTypes_h

View file

@ -0,0 +1,27 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "AccessibleOrProxy.h"
AccessibleOrProxy
AccessibleOrProxy::Parent() const
{
if (IsAccessible()) {
return AsAccessible()->Parent();
}
ProxyAccessible* proxy = AsProxy();
if (!proxy) {
return nullptr;
}
if (ProxyAccessible* parent = proxy->Parent()) {
return parent;
}
// Otherwise this should be the proxy for the tab's top level document.
return proxy->OuterDocOfRemoteBrowser();
}

View file

@ -0,0 +1,123 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_AccessibleOrProxy_h
#define mozilla_a11y_AccessibleOrProxy_h
#include "mozilla/a11y/Accessible.h"
#include "mozilla/a11y/ProxyAccessible.h"
#include "mozilla/a11y/Role.h"
#include <stdint.h>
namespace mozilla {
namespace a11y {
/**
* This class stores an Accessible* or a ProxyAccessible* in a safe manner
* with size sizeof(void*).
*/
class AccessibleOrProxy
{
public:
MOZ_IMPLICIT AccessibleOrProxy(Accessible* aAcc) :
mBits(reinterpret_cast<uintptr_t>(aAcc)) {}
MOZ_IMPLICIT AccessibleOrProxy(ProxyAccessible* aProxy) :
mBits(aProxy ? (reinterpret_cast<uintptr_t>(aProxy) | IS_PROXY) : 0) {}
MOZ_IMPLICIT AccessibleOrProxy(decltype(nullptr)) : mBits(0) {}
bool IsProxy() const { return mBits & IS_PROXY; }
ProxyAccessible* AsProxy() const
{
if (IsProxy()) {
return reinterpret_cast<ProxyAccessible*>(mBits & ~IS_PROXY);
}
return nullptr;
}
bool IsAccessible() const { return !IsProxy(); }
Accessible* AsAccessible() const
{
if (IsAccessible()) {
return reinterpret_cast<Accessible*>(mBits);
}
return nullptr;
}
bool IsNull() const { return mBits == 0; }
uint32_t ChildCount() const
{
if (IsProxy()) {
return AsProxy()->ChildrenCount();
}
return AsAccessible()->ChildCount();
}
/**
* Return the child object either an accessible or a proxied accessible at
* the given index.
*/
AccessibleOrProxy ChildAt(uint32_t aIdx)
{
if (IsProxy()) {
return AsProxy()->ChildAt(aIdx);
}
return AsAccessible()->GetChildAt(aIdx);
}
/**
* Return the first child object.
*/
AccessibleOrProxy FirstChild()
{
if (IsProxy()) {
return AsProxy()->FirstChild();
}
return AsAccessible()->FirstChild();
}
/**
* Return the first child object.
*/
AccessibleOrProxy LastChild()
{
if (IsProxy()) {
return AsProxy()->LastChild();
}
return AsAccessible()->LastChild();
}
role Role() const
{
if (IsProxy()) {
return AsProxy()->Role();
}
return AsAccessible()->Role();
}
AccessibleOrProxy Parent() const;
// XXX these are implementation details that ideally would not be exposed.
uintptr_t Bits() const { return mBits; }
void SetBits(uintptr_t aBits) { mBits = aBits; }
private:
uintptr_t mBits;
static const uintptr_t IS_PROXY = 0x1;
};
}
}
#endif

View file

@ -0,0 +1,26 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "nsIAccessibleRelation.h"
#include "nsIAccessibleRole.h"
#include "RelationType.h"
#include "Role.h"
using namespace mozilla::a11y;
#define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \
static_assert(static_cast<uint32_t>(roles::geckoRole) \
== static_cast<uint32_t>(nsIAccessibleRole::ROLE_ ## geckoRole), \
"internal and xpcom roles differ!");
#include "RoleMap.h"
#undef ROLE
#define RELATIONTYPE(geckoType, stringType, atkType, msaaType, ia2Type) \
static_assert(static_cast<uint32_t>(RelationType::geckoType) \
== static_cast<uint32_t>(nsIAccessibleRelation::RELATION_ ## geckoType), \
"internal and xpcom relations differ!");
#include "RelationTypeMap.h"
#undef RELATIONTYPE

View file

@ -0,0 +1,594 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 "DocManager.h"
#include "ApplicationAccessible.h"
#include "ARIAMap.h"
#include "DocAccessible-inl.h"
#include "DocAccessibleChild.h"
#include "DocAccessibleParent.h"
#include "nsAccessibilityService.h"
#include "Platform.h"
#include "RootAccessibleWrap.h"
#include "xpcAccessibleDocument.h"
#ifdef A11Y_LOG
#include "Logging.h"
#endif
#include "mozilla/EventListenerManager.h"
#include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
#include "nsCURILoader.h"
#include "nsDocShellLoadTypes.h"
#include "nsIChannel.h"
#include "nsIDOMDocument.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIWebNavigation.h"
#include "nsServiceManagerUtils.h"
#include "nsIWebProgress.h"
#include "nsCoreUtils.h"
#include "nsXULAppAPI.h"
#include "mozilla/dom/TabChild.h"
using namespace mozilla;
using namespace mozilla::a11y;
using namespace mozilla::dom;
StaticAutoPtr<nsTArray<DocAccessibleParent*>> DocManager::sRemoteDocuments;
nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
DocManager::sRemoteXPCDocumentCache = nullptr;
////////////////////////////////////////////////////////////////////////////////
// DocManager
////////////////////////////////////////////////////////////////////////////////
DocManager::DocManager()
: mDocAccessibleCache(2), mXPCDocumentCache(0)
{
}
////////////////////////////////////////////////////////////////////////////////
// DocManager public
DocAccessible*
DocManager::GetDocAccessible(nsIDocument* aDocument)
{
if (!aDocument)
return nullptr;
DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
if (docAcc)
return docAcc;
return CreateDocOrRootAccessible(aDocument);
}
Accessible*
DocManager::FindAccessibleInCache(nsINode* aNode) const
{
for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
DocAccessible* docAccessible = iter.UserData();
NS_ASSERTION(docAccessible,
"No doc accessible for the object in doc accessible cache!");
if (docAccessible) {
Accessible* accessible = docAccessible->GetAccessible(aNode);
if (accessible) {
return accessible;
}
}
}
return nullptr;
}
void
DocManager::NotifyOfDocumentShutdown(DocAccessible* aDocument,
nsIDocument* aDOMDocument)
{
// We need to remove listeners in both cases, when document is being shutdown
// or when accessibility service is being shut down as well.
RemoveListeners(aDOMDocument);
// Document will already be removed when accessibility service is shutting
// down so we do not need to remove it twice.
if (nsAccessibilityService::IsShutdown()) {
return;
}
xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
if (xpcDoc) {
xpcDoc->Shutdown();
mXPCDocumentCache.Remove(aDocument);
}
mDocAccessibleCache.Remove(aDOMDocument);
}
void
DocManager::NotifyOfRemoteDocShutdown(DocAccessibleParent* aDoc)
{
xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
if (doc) {
doc->Shutdown();
sRemoteXPCDocumentCache->Remove(aDoc);
}
}
xpcAccessibleDocument*
DocManager::GetXPCDocument(DocAccessible* aDocument)
{
if (!aDocument)
return nullptr;
xpcAccessibleDocument* xpcDoc = mXPCDocumentCache.GetWeak(aDocument);
if (!xpcDoc) {
xpcDoc = new xpcAccessibleDocument(aDocument);
mXPCDocumentCache.Put(aDocument, xpcDoc);
}
return xpcDoc;
}
xpcAccessibleDocument*
DocManager::GetXPCDocument(DocAccessibleParent* aDoc)
{
xpcAccessibleDocument* doc = GetCachedXPCDocument(aDoc);
if (doc) {
return doc;
}
if (!sRemoteXPCDocumentCache) {
sRemoteXPCDocumentCache =
new nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>;
}
doc =
new xpcAccessibleDocument(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
sRemoteXPCDocumentCache->Put(aDoc, doc);
return doc;
}
#ifdef DEBUG
bool
DocManager::IsProcessingRefreshDriverNotification() const
{
for (auto iter = mDocAccessibleCache.ConstIter(); !iter.Done(); iter.Next()) {
DocAccessible* docAccessible = iter.UserData();
NS_ASSERTION(docAccessible,
"No doc accessible for the object in doc accessible cache!");
if (docAccessible && docAccessible->mNotificationController &&
docAccessible->mNotificationController->IsUpdating()) {
return true;
}
}
return false;
}
#endif
////////////////////////////////////////////////////////////////////////////////
// DocManager protected
bool
DocManager::Init()
{
nsCOMPtr<nsIWebProgress> progress =
do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
if (!progress)
return false;
progress->AddProgressListener(static_cast<nsIWebProgressListener*>(this),
nsIWebProgress::NOTIFY_STATE_DOCUMENT);
return true;
}
void
DocManager::Shutdown()
{
nsCOMPtr<nsIWebProgress> progress =
do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
if (progress)
progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this));
ClearDocCache();
}
////////////////////////////////////////////////////////////////////////////////
// nsISupports
NS_IMPL_ISUPPORTS(DocManager,
nsIWebProgressListener,
nsIDOMEventListener,
nsISupportsWeakReference)
////////////////////////////////////////////////////////////////////////////////
// nsIWebProgressListener
NS_IMETHODIMP
DocManager::OnStateChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, uint32_t aStateFlags,
nsresult aStatus)
{
NS_ASSERTION(aStateFlags & STATE_IS_DOCUMENT, "Other notifications excluded");
if (nsAccessibilityService::IsShutdown() || !aWebProgress ||
(aStateFlags & (STATE_START | STATE_STOP)) == 0)
return NS_OK;
nsCOMPtr<mozIDOMWindowProxy> DOMWindow;
aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow));
NS_ENSURE_STATE(DOMWindow);
nsPIDOMWindowOuter* piWindow = nsPIDOMWindowOuter::From(DOMWindow);
MOZ_ASSERT(piWindow);
nsCOMPtr<nsIDocument> document = piWindow->GetDoc();
NS_ENSURE_STATE(document);
// Document was loaded.
if (aStateFlags & STATE_STOP) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eDocLoad))
logging::DocLoad("document loaded", aWebProgress, aRequest, aStateFlags);
#endif
// Figure out an event type to notify the document has been loaded.
uint32_t eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED;
// Some XUL documents get start state and then stop state with failure
// status when everything is ok. Fire document load complete event in this
// case.
if (NS_SUCCEEDED(aStatus) || !nsCoreUtils::IsContentDocument(document))
eventType = nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE;
// If end consumer has been retargeted for loaded content then do not fire
// any event because it means no new document has been loaded, for example,
// it happens when user clicks on file link.
if (aRequest) {
uint32_t loadFlags = 0;
aRequest->GetLoadFlags(&loadFlags);
if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI)
eventType = 0;
}
HandleDOMDocumentLoad(document, eventType);
return NS_OK;
}
// Document loading was started.
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eDocLoad))
logging::DocLoad("start document loading", aWebProgress, aRequest, aStateFlags);
#endif
DocAccessible* docAcc = GetExistingDocAccessible(document);
if (!docAcc)
return NS_OK;
nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow));
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav));
NS_ENSURE_STATE(docShell);
bool isReloading = false;
uint32_t loadType;
docShell->GetLoadType(&loadType);
if (loadType == LOAD_RELOAD_NORMAL ||
loadType == LOAD_RELOAD_BYPASS_CACHE ||
loadType == LOAD_RELOAD_BYPASS_PROXY ||
loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE ||
loadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) {
isReloading = true;
}
docAcc->NotifyOfLoading(isReloading);
return NS_OK;
}
NS_IMETHODIMP
DocManager::OnProgressChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
int32_t aCurSelfProgress,
int32_t aMaxSelfProgress,
int32_t aCurTotalProgress,
int32_t aMaxTotalProgress)
{
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
return NS_OK;
}
NS_IMETHODIMP
DocManager::OnLocationChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, nsIURI* aLocation,
uint32_t aFlags)
{
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
return NS_OK;
}
NS_IMETHODIMP
DocManager::OnStatusChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest, nsresult aStatus,
const char16_t* aMessage)
{
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
return NS_OK;
}
NS_IMETHODIMP
DocManager::OnSecurityChange(nsIWebProgress* aWebProgress,
nsIRequest* aRequest,
uint32_t aState)
{
NS_NOTREACHED("notification excluded in AddProgressListener(...)");
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// nsIDOMEventListener
NS_IMETHODIMP
DocManager::HandleEvent(nsIDOMEvent* aEvent)
{
nsAutoString type;
aEvent->GetType(type);
nsCOMPtr<nsIDocument> document =
do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
NS_ASSERTION(document, "pagehide or DOMContentLoaded for non document!");
if (!document)
return NS_OK;
if (type.EqualsLiteral("pagehide")) {
// 'pagehide' event is registered on every DOM document we create an
// accessible for, process the event for the target. This document
// accessible and all its sub document accessible are shutdown as result of
// processing.
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eDocDestroy))
logging::DocDestroy("received 'pagehide' event", document);
#endif
// Shutdown this one and sub document accessibles.
// We're allowed to not remove listeners when accessible document is
// shutdown since we don't keep strong reference on chrome event target and
// listeners are removed automatically when chrome event target goes away.
DocAccessible* docAccessible = GetExistingDocAccessible(document);
if (docAccessible)
docAccessible->Shutdown();
return NS_OK;
}
// XXX: handle error pages loading separately since they get neither
// webprogress notifications nor 'pageshow' event.
if (type.EqualsLiteral("DOMContentLoaded") &&
nsCoreUtils::IsErrorPage(document)) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eDocLoad))
logging::DocLoad("handled 'DOMContentLoaded' event", document);
#endif
HandleDOMDocumentLoad(document,
nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE);
}
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
// DocManager private
void
DocManager::HandleDOMDocumentLoad(nsIDocument* aDocument,
uint32_t aLoadEventType)
{
// Document accessible can be created before we were notified the DOM document
// was loaded completely. However if it's not created yet then create it.
DocAccessible* docAcc = GetExistingDocAccessible(aDocument);
if (!docAcc) {
docAcc = CreateDocOrRootAccessible(aDocument);
if (!docAcc)
return;
}
docAcc->NotifyOfLoad(aLoadEventType);
}
void
DocManager::AddListeners(nsIDocument* aDocument,
bool aAddDOMContentLoadedListener)
{
nsPIDOMWindowOuter* window = aDocument->GetWindow();
EventTarget* target = window->GetChromeEventHandler();
EventListenerManager* elm = target->GetOrCreateListenerManager();
elm->AddEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
TrustedEventsAtCapture());
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eDocCreate))
logging::Text("added 'pagehide' listener");
#endif
if (aAddDOMContentLoadedListener) {
elm->AddEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
TrustedEventsAtCapture());
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eDocCreate))
logging::Text("added 'DOMContentLoaded' listener");
#endif
}
}
void
DocManager::RemoveListeners(nsIDocument* aDocument)
{
nsPIDOMWindowOuter* window = aDocument->GetWindow();
if (!window)
return;
EventTarget* target = window->GetChromeEventHandler();
if (!target)
return;
EventListenerManager* elm = target->GetOrCreateListenerManager();
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
TrustedEventsAtCapture());
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
TrustedEventsAtCapture());
}
DocAccessible*
DocManager::CreateDocOrRootAccessible(nsIDocument* aDocument)
{
// Ignore hiding, resource documents and documents without docshell.
if (!aDocument->IsVisibleConsideringAncestors() ||
aDocument->IsResourceDoc() || !aDocument->IsActive())
return nullptr;
// Ignore documents without presshell and not having root frame.
nsIPresShell* presShell = aDocument->GetShell();
if (!presShell || presShell->IsDestroying())
return nullptr;
bool isRootDoc = nsCoreUtils::IsRootDocument(aDocument);
DocAccessible* parentDocAcc = nullptr;
if (!isRootDoc) {
// XXXaaronl: ideally we would traverse the presshell chain. Since there's
// no easy way to do that, we cheat and use the document hierarchy.
parentDocAcc = GetDocAccessible(aDocument->GetParentDocument());
NS_ASSERTION(parentDocAcc,
"Can't create an accessible for the document!");
if (!parentDocAcc)
return nullptr;
}
// We only create root accessibles for the true root, otherwise create a
// doc accessible.
RefPtr<DocAccessible> docAcc = isRootDoc ?
new RootAccessibleWrap(aDocument, presShell) :
new DocAccessibleWrap(aDocument, presShell);
// Cache the document accessible into document cache.
mDocAccessibleCache.Put(aDocument, docAcc);
// Initialize the document accessible.
docAcc->Init();
// Bind the document to the tree.
if (isRootDoc) {
if (!ApplicationAcc()->AppendChild(docAcc)) {
docAcc->Shutdown();
return nullptr;
}
// Fire reorder event to notify new accessible document has been attached to
// the tree. The reorder event is delivered after the document tree is
// constructed because event processing and tree construction are done by
// the same document.
// Note: don't use AccReorderEvent to avoid coalsecense and special reorder
// events processing.
docAcc->FireDelayedEvent(nsIAccessibleEvent::EVENT_REORDER,
ApplicationAcc());
if (IPCAccessibilityActive()) {
nsIDocShell* docShell = aDocument->GetDocShell();
if (docShell) {
nsCOMPtr<nsITabChild> tabChild = docShell->GetTabChild();
// XXX We may need to handle the case that we don't have a tab child
// differently. It may be that this will cause us to fail to notify
// the parent process about important accessible documents.
if (tabChild) {
DocAccessibleChild* ipcDoc = new DocAccessibleChild(docAcc);
docAcc->SetIPCDoc(ipcDoc);
#if defined(XP_WIN)
IAccessibleHolder holder(CreateHolderFromAccessible(docAcc));
#endif
static_cast<TabChild*>(tabChild.get())->
SendPDocAccessibleConstructor(ipcDoc, nullptr, 0,
#if defined(XP_WIN)
AccessibleWrap::GetChildIDFor(docAcc),
holder
#else
0, 0
#endif
);
}
}
}
} else {
parentDocAcc->BindChildDocument(docAcc);
}
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eDocCreate)) {
logging::DocCreate("document creation finished", aDocument);
logging::Stack();
}
#endif
AddListeners(aDocument, isRootDoc);
return docAcc;
}
////////////////////////////////////////////////////////////////////////////////
// DocManager static
void
DocManager::ClearDocCache()
{
while (mDocAccessibleCache.Count() > 0) {
auto iter = mDocAccessibleCache.Iter();
MOZ_ASSERT(!iter.Done());
DocAccessible* docAcc = iter.UserData();
NS_ASSERTION(docAcc,
"No doc accessible for the object in doc accessible cache!");
if (docAcc) {
docAcc->Shutdown();
}
iter.Remove();
}
// Ensure that all xpcom accessible documents are shut down as well.
while (mXPCDocumentCache.Count() > 0) {
auto iter = mXPCDocumentCache.Iter();
MOZ_ASSERT(!iter.Done());
xpcAccessibleDocument* xpcDoc = iter.UserData();
NS_ASSERTION(xpcDoc, "No xpc doc for the object in xpc doc cache!");
if (xpcDoc) {
xpcDoc->Shutdown();
}
iter.Remove();
}
}
void
DocManager::RemoteDocAdded(DocAccessibleParent* aDoc)
{
if (!sRemoteDocuments) {
sRemoteDocuments = new nsTArray<DocAccessibleParent*>;
ClearOnShutdown(&sRemoteDocuments);
}
MOZ_ASSERT(!sRemoteDocuments->Contains(aDoc),
"How did we already have the doc!");
sRemoteDocuments->AppendElement(aDoc);
ProxyCreated(aDoc, Interfaces::DOCUMENT | Interfaces::HYPERTEXT);
}

View file

@ -0,0 +1,189 @@
/* 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/. */
#ifndef mozilla_a11_DocManager_h_
#define mozilla_a11_DocManager_h_
#include "mozilla/ClearOnShutdown.h"
#include "nsIDocument.h"
#include "nsIDOMEventListener.h"
#include "nsRefPtrHashtable.h"
#include "nsIWebProgressListener.h"
#include "nsWeakReference.h"
#include "nsIPresShell.h"
#include "mozilla/StaticPtr.h"
namespace mozilla {
namespace a11y {
class Accessible;
class DocAccessible;
class xpcAccessibleDocument;
class DocAccessibleParent;
/**
* Manage the document accessible life cycle.
*/
class DocManager : public nsIWebProgressListener,
public nsIDOMEventListener,
public nsSupportsWeakReference
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIWEBPROGRESSLISTENER
NS_DECL_NSIDOMEVENTLISTENER
/**
* Return document accessible for the given DOM node.
*/
DocAccessible* GetDocAccessible(nsIDocument* aDocument);
/**
* Return document accessible for the given presshell.
*/
DocAccessible* GetDocAccessible(const nsIPresShell* aPresShell)
{
if (!aPresShell)
return nullptr;
DocAccessible* doc = aPresShell->GetDocAccessible();
if (doc)
return doc;
return GetDocAccessible(aPresShell->GetDocument());
}
/**
* Search through all document accessibles for an accessible with the given
* unique id.
*/
Accessible* FindAccessibleInCache(nsINode* aNode) const;
/**
* Called by document accessible when it gets shutdown.
*/
void NotifyOfDocumentShutdown(DocAccessible* aDocument,
nsIDocument* aDOMDocument);
/**
* Return XPCOM accessible document.
*/
xpcAccessibleDocument* GetXPCDocument(DocAccessible* aDocument);
xpcAccessibleDocument* GetCachedXPCDocument(DocAccessible* aDocument) const
{ return mXPCDocumentCache.GetWeak(aDocument); }
/*
* Notification that a top level document in a content process has gone away.
*/
static void RemoteDocShutdown(DocAccessibleParent* aDoc)
{
DebugOnly<bool> result = sRemoteDocuments->RemoveElement(aDoc);
MOZ_ASSERT(result, "Why didn't we find the document!");
}
/*
* Notify of a new top level document in a content process.
*/
static void RemoteDocAdded(DocAccessibleParent* aDoc);
static const nsTArray<DocAccessibleParent*>* TopLevelRemoteDocs()
{ return sRemoteDocuments; }
/**
* Remove the xpc document for a remote document if there is one.
*/
static void NotifyOfRemoteDocShutdown(DocAccessibleParent* adoc);
/**
* Get a XPC document for a remote document.
*/
static xpcAccessibleDocument* GetXPCDocument(DocAccessibleParent* aDoc);
static xpcAccessibleDocument* GetCachedXPCDocument(const DocAccessibleParent* aDoc)
{
return sRemoteXPCDocumentCache ? sRemoteXPCDocumentCache->GetWeak(aDoc)
: nullptr;
}
#ifdef DEBUG
bool IsProcessingRefreshDriverNotification() const;
#endif
protected:
DocManager();
virtual ~DocManager() { }
/**
* Initialize the manager.
*/
bool Init();
/**
* Shutdown the manager.
*/
void Shutdown();
private:
DocManager(const DocManager&);
DocManager& operator =(const DocManager&);
private:
/**
* Create an accessible document if it was't created and fire accessibility
* events if needed.
*
* @param aDocument [in] loaded DOM document
* @param aLoadEventType [in] specifies the event type to fire load event,
* if 0 then no event is fired
*/
void HandleDOMDocumentLoad(nsIDocument* aDocument,
uint32_t aLoadEventType);
/**
* Add/remove 'pagehide' and 'DOMContentLoaded' event listeners.
*/
void AddListeners(nsIDocument *aDocument, bool aAddPageShowListener);
void RemoveListeners(nsIDocument* aDocument);
/**
* Create document or root accessible.
*/
DocAccessible* CreateDocOrRootAccessible(nsIDocument* aDocument);
/**
* Clear the cache and shutdown the document accessibles.
*/
void ClearDocCache();
typedef nsRefPtrHashtable<nsPtrHashKey<const nsIDocument>, DocAccessible>
DocAccessibleHashtable;
DocAccessibleHashtable mDocAccessibleCache;
typedef nsRefPtrHashtable<nsPtrHashKey<const DocAccessible>, xpcAccessibleDocument>
XPCDocumentHashtable;
XPCDocumentHashtable mXPCDocumentCache;
static nsRefPtrHashtable<nsPtrHashKey<const DocAccessibleParent>, xpcAccessibleDocument>*
sRemoteXPCDocumentCache;
/*
* The list of remote top level documents.
*/
static StaticAutoPtr<nsTArray<DocAccessibleParent*>> sRemoteDocuments;
};
/**
* Return the existing document accessible for the document if any.
* Note this returns the doc accessible for the primary pres shell if there is
* more than one.
*/
inline DocAccessible*
GetExistingDocAccessible(const nsIDocument* aDocument)
{
nsIPresShell* ps = aDocument->GetShell();
return ps ? ps->GetDocAccessible() : nullptr;
}
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11_DocManager_h_

View file

@ -0,0 +1,81 @@
/* 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 "EmbeddedObjCollector.h"
#include "Accessible.h"
using namespace mozilla::a11y;
uint32_t
EmbeddedObjCollector::Count()
{
EnsureNGetIndex(nullptr);
return mObjects.Length();
}
Accessible*
EmbeddedObjCollector::GetAccessibleAt(uint32_t aIndex)
{
Accessible* accessible = mObjects.SafeElementAt(aIndex, nullptr);
if (accessible)
return accessible;
return EnsureNGetObject(aIndex);
}
Accessible*
EmbeddedObjCollector::EnsureNGetObject(uint32_t aIndex)
{
uint32_t childCount = mRoot->ChildCount();
while (mRootChildIdx < childCount) {
Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
if (child->IsText())
continue;
AppendObject(child);
if (mObjects.Length() - 1 == aIndex)
return mObjects[aIndex];
}
return nullptr;
}
int32_t
EmbeddedObjCollector::EnsureNGetIndex(Accessible* aAccessible)
{
uint32_t childCount = mRoot->ChildCount();
while (mRootChildIdx < childCount) {
Accessible* child = mRoot->GetChildAt(mRootChildIdx++);
if (child->IsText())
continue;
AppendObject(child);
if (child == aAccessible)
return mObjects.Length() - 1;
}
return -1;
}
int32_t
EmbeddedObjCollector::GetIndexAt(Accessible* aAccessible)
{
if (aAccessible->mParent != mRoot)
return -1;
MOZ_ASSERT(!aAccessible->IsProxy());
if (aAccessible->mInt.mIndexOfEmbeddedChild != -1)
return aAccessible->mInt.mIndexOfEmbeddedChild;
return !aAccessible->IsText() ? EnsureNGetIndex(aAccessible) : -1;
}
void
EmbeddedObjCollector::AppendObject(Accessible* aAccessible)
{
MOZ_ASSERT(!aAccessible->IsProxy());
aAccessible->mInt.mIndexOfEmbeddedChild = mObjects.Length();
mObjects.AppendElement(aAccessible);
}

View file

@ -0,0 +1,69 @@
/* 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/. */
#ifndef mozilla_a11y_EmbeddedObjCollector_h__
#define mozilla_a11y_EmbeddedObjCollector_h__
#include "nsTArray.h"
namespace mozilla {
namespace a11y {
class Accessible;
/**
* Collect embedded objects. Provide quick access to accessible by index and
* vice versa.
*/
class EmbeddedObjCollector final
{
public:
~EmbeddedObjCollector() { }
/**
* Return index of the given accessible within the collection.
*/
int32_t GetIndexAt(Accessible* aAccessible);
/**
* Return accessible count within the collection.
*/
uint32_t Count();
/**
* Return an accessible from the collection at the given index.
*/
Accessible* GetAccessibleAt(uint32_t aIndex);
protected:
/**
* Ensure accessible at the given index is stored and return it.
*/
Accessible* EnsureNGetObject(uint32_t aIndex);
/**
* Ensure index for the given accessible is stored and return it.
*/
int32_t EnsureNGetIndex(Accessible* aAccessible);
// Make sure it's used by Accessible class only.
explicit EmbeddedObjCollector(Accessible* aRoot) :
mRoot(aRoot), mRootChildIdx(0) {}
/**
* Append the object to collection.
*/
void AppendObject(Accessible* aAccessible);
friend class Accessible;
Accessible* mRoot;
uint32_t mRootChildIdx;
nsTArray<Accessible*> mObjects;
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,344 @@
/* -*- 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 "EventQueue.h"
#include "Accessible-inl.h"
#include "nsEventShell.h"
#include "DocAccessible.h"
#include "DocAccessibleChild.h"
#include "nsAccessibilityService.h"
#include "nsTextEquivUtils.h"
#ifdef A11Y_LOG
#include "Logging.h"
#endif
using namespace mozilla;
using namespace mozilla::a11y;
// Defines the number of selection add/remove events in the queue when they
// aren't packed into single selection within event.
const unsigned int kSelChangeCountToPack = 5;
////////////////////////////////////////////////////////////////////////////////
// EventQueue
////////////////////////////////////////////////////////////////////////////////
bool
EventQueue::PushEvent(AccEvent* aEvent)
{
NS_ASSERTION((aEvent->mAccessible && aEvent->mAccessible->IsApplication()) ||
aEvent->Document() == mDocument,
"Queued event belongs to another document!");
if (!mEvents.AppendElement(aEvent))
return false;
// Filter events.
CoalesceEvents();
if (aEvent->mEventRule != AccEvent::eDoNotEmit &&
(aEvent->mEventType == nsIAccessibleEvent::EVENT_NAME_CHANGE ||
aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_REMOVED ||
aEvent->mEventType == nsIAccessibleEvent::EVENT_TEXT_INSERTED)) {
PushNameChange(aEvent->mAccessible);
}
return true;
}
bool
EventQueue::PushNameChange(Accessible* aTarget)
{
// Fire name change event on parent given that this event hasn't been
// coalesced, the parent's name was calculated from its subtree, and the
// subtree was changed.
if (aTarget->HasNameDependentParent()) {
// Only continue traversing up the tree if it's possible that the parent
// accessible's name can depend on this accessible's name.
Accessible* parent = aTarget->Parent();
while (parent &&
nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeIfReqRule)) {
// Test possible name dependent parent.
if (nsTextEquivUtils::HasNameRule(parent, eNameFromSubtreeRule)) {
nsAutoString name;
ENameValueFlag nameFlag = parent->Name(name);
// If name is obtained from subtree, fire name change event.
if (nameFlag == eNameFromSubtree) {
RefPtr<AccEvent> nameChangeEvent =
new AccEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, parent);
return PushEvent(nameChangeEvent);
}
break;
}
parent = parent->Parent();
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// EventQueue: private
void
EventQueue::CoalesceEvents()
{
NS_ASSERTION(mEvents.Length(), "There should be at least one pending event!");
uint32_t tail = mEvents.Length() - 1;
AccEvent* tailEvent = mEvents[tail];
switch(tailEvent->mEventRule) {
case AccEvent::eCoalesceReorder:
{
DebugOnly<Accessible*> target = tailEvent->mAccessible.get();
MOZ_ASSERT(target->IsApplication() ||
target->IsOuterDoc() ||
target->IsXULTree(),
"Only app or outerdoc accessible reorder events are in the queue");
MOZ_ASSERT(tailEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER, "only reorder events should be queued");
break; // case eCoalesceReorder
}
case AccEvent::eCoalesceOfSameType:
{
// Coalesce old events by newer event.
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule) {
accEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
}
} break; // case eCoalesceOfSameType
case AccEvent::eCoalesceSelectionChange:
{
AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule == tailEvent->mEventRule) {
AccSelChangeEvent* thisSelChangeEvent =
downcast_accEvent(thisEvent);
// Coalesce selection change events within same control.
if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
return;
}
}
}
} break; // eCoalesceSelectionChange
case AccEvent::eCoalesceStateChange:
{
// If state change event is duped then ignore previous event. If state
// change event is opposite to previous event then no event is emitted
// (accessible state wasn't changed).
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
thisEvent->mEventType == tailEvent->mEventType &&
thisEvent->mAccessible == tailEvent->mAccessible) {
AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent);
AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent);
if (thisSCEvent->mState == tailSCEvent->mState) {
thisEvent->mEventRule = AccEvent::eDoNotEmit;
if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled)
tailEvent->mEventRule = AccEvent::eDoNotEmit;
}
}
}
break; // eCoalesceStateChange
}
case AccEvent::eCoalesceTextSelChange:
{
// Coalesce older event by newer event for the same selection or target.
// Events for same selection may have different targets and vice versa one
// target may be pointed by different selections (for latter see
// bug 927159).
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
thisEvent->mEventType == tailEvent->mEventType) {
AccTextSelChangeEvent* thisTSCEvent = downcast_accEvent(thisEvent);
AccTextSelChangeEvent* tailTSCEvent = downcast_accEvent(tailEvent);
if (thisTSCEvent->mSel == tailTSCEvent->mSel ||
thisEvent->mAccessible == tailEvent->mAccessible)
thisEvent->mEventRule = AccEvent::eDoNotEmit;
}
}
} break; // eCoalesceTextSelChange
case AccEvent::eRemoveDupes:
{
// Check for repeat events, coalesce newly appended event by more older
// event.
for (uint32_t index = tail - 1; index < tail; index--) {
AccEvent* accEvent = mEvents[index];
if (accEvent->mEventType == tailEvent->mEventType &&
accEvent->mEventRule == tailEvent->mEventRule &&
accEvent->mAccessible == tailEvent->mAccessible) {
tailEvent->mEventRule = AccEvent::eDoNotEmit;
return;
}
}
} break; // case eRemoveDupes
default:
break; // case eAllowDupes, eDoNotEmit
} // switch
}
void
EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
AccSelChangeEvent* aThisEvent,
uint32_t aThisIndex)
{
aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
// Pack all preceding events into single selection within event
// when we receive too much selection add/remove events.
if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
aTailEvent->mAccessible = aTailEvent->mWidget;
aThisEvent->mEventRule = AccEvent::eDoNotEmit;
// Do not emit any preceding selection events for same widget if they
// weren't coalesced yet.
if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
for (uint32_t jdx = aThisIndex - 1; jdx < aThisIndex; jdx--) {
AccEvent* prevEvent = mEvents[jdx];
if (prevEvent->mEventRule == aTailEvent->mEventRule) {
AccSelChangeEvent* prevSelChangeEvent =
downcast_accEvent(prevEvent);
if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
}
}
}
return;
}
// Pack sequential selection remove and selection add events into
// single selection change event.
if (aTailEvent->mPreceedingCount == 1 &&
aTailEvent->mItem != aThisEvent->mItem) {
if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aThisEvent->mEventRule = AccEvent::eDoNotEmit;
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aTailEvent->mPackedEvent = aThisEvent;
return;
}
if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aTailEvent->mEventRule = AccEvent::eDoNotEmit;
aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aThisEvent->mPackedEvent = aTailEvent;
return;
}
}
// Unpack the packed selection change event because we've got one
// more selection add/remove.
if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
if (aThisEvent->mPackedEvent) {
aThisEvent->mPackedEvent->mEventType =
aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
nsIAccessibleEvent::EVENT_SELECTION_ADD :
nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
aThisEvent->mPackedEvent->mEventRule =
AccEvent::eCoalesceSelectionChange;
aThisEvent->mPackedEvent = nullptr;
}
aThisEvent->mEventType =
aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
nsIAccessibleEvent::EVENT_SELECTION_ADD :
nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
return;
}
// Convert into selection add since control has single selection but other
// selection events for this control are queued.
if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
}
////////////////////////////////////////////////////////////////////////////////
// EventQueue: event queue
void
EventQueue::ProcessEventQueue()
{
// Process only currently queued events.
nsTArray<RefPtr<AccEvent> > events;
events.SwapElements(mEvents);
uint32_t eventCount = events.Length();
#ifdef A11Y_LOG
if (eventCount > 0 && logging::IsEnabled(logging::eEvents)) {
logging::MsgBegin("EVENTS", "events processing");
logging::Address("document", mDocument);
logging::MsgEnd();
}
#endif
for (uint32_t idx = 0; idx < eventCount; idx++) {
AccEvent* event = events[idx];
if (event->mEventRule != AccEvent::eDoNotEmit) {
Accessible* target = event->GetAccessible();
if (!target || target->IsDefunct())
continue;
// Dispatch the focus event if target is still focused.
if (event->mEventType == nsIAccessibleEvent::EVENT_FOCUS) {
FocusMgr()->ProcessFocusEvent(event);
continue;
}
// Dispatch caret moved and text selection change events.
if (event->mEventType == nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED) {
SelectionMgr()->ProcessTextSelChangeEvent(event);
continue;
}
// Fire selected state change events in support to selection events.
if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) {
nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
true, event->mIsFromUserInput);
} else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) {
nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
false, event->mIsFromUserInput);
} else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
AccSelChangeEvent* selChangeEvent = downcast_accEvent(event);
nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
(selChangeEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
event->mIsFromUserInput);
if (selChangeEvent->mPackedEvent) {
nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible,
states::SELECTED,
(selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
selChangeEvent->mPackedEvent->mIsFromUserInput);
}
}
nsEventShell::FireEvent(event);
}
if (!mDocument)
return;
}
}

View file

@ -0,0 +1,77 @@
/* -*- 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/. */
#ifndef mozilla_a11y_EventQueue_h_
#define mozilla_a11y_EventQueue_h_
#include "AccEvent.h"
namespace mozilla {
namespace a11y {
class DocAccessible;
/**
* Used to organize and coalesce pending events.
*/
class EventQueue
{
protected:
explicit EventQueue(DocAccessible* aDocument) : mDocument(aDocument) { }
/**
* Put an accessible event into the queue to process it later.
*/
bool PushEvent(AccEvent* aEvent);
/**
* Puts a name change event into the queue, if needed.
*/
bool PushNameChange(Accessible* aTarget);
/**
* Process events from the queue and fires events.
*/
void ProcessEventQueue();
private:
EventQueue(const EventQueue&) = delete;
EventQueue& operator = (const EventQueue&) = delete;
// Event queue processing
/**
* Coalesce redundant events from the queue.
*/
void CoalesceEvents();
/**
* Coalesce events from the same subtree.
*/
void CoalesceReorderEvents(AccEvent* aTailEvent);
/**
* Coalesce two selection change events within the same select control.
*/
void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
AccSelChangeEvent* aThisEvent,
uint32_t aThisIndex);
protected:
/**
* The document accessible reference owning this queue.
*/
DocAccessible* mDocument;
/**
* Pending events array. Don't make this an AutoTArray; we use
* SwapElements() on it.
*/
nsTArray<RefPtr<AccEvent>> mEvents;
};
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11y_EventQueue_h_

View file

@ -0,0 +1,618 @@
/* -*- 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 "EventTree.h"
#include "Accessible-inl.h"
#include "nsEventShell.h"
#include "DocAccessible.h"
#ifdef A11Y_LOG
#include "Logging.h"
#endif
#include "mozilla/UniquePtr.h"
using namespace mozilla;
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// TreeMutation class
EventTree* const TreeMutation::kNoEventTree = reinterpret_cast<EventTree*>(-1);
TreeMutation::TreeMutation(Accessible* aParent, bool aNoEvents) :
mParent(aParent), mStartIdx(UINT32_MAX),
mStateFlagsCopy(mParent->mStateFlags),
mQueueEvents(!aNoEvents)
{
#ifdef DEBUG
mIsDone = false;
#endif
#ifdef A11Y_LOG
if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
logging::MsgBegin("EVENTS_TREE", "reordering tree before");
logging::AccessibleInfo("reordering for", mParent);
Controller()->RootEventTree().Log();
logging::MsgEnd();
if (logging::IsEnabled(logging::eVerbose)) {
logging::Tree("EVENTS_TREE", "Container tree", mParent->Document(),
PrefixLog, static_cast<void*>(this));
}
}
#endif
mParent->mStateFlags |= Accessible::eKidsMutating;
}
TreeMutation::~TreeMutation()
{
MOZ_ASSERT(mIsDone, "Done() must be called explicitly");
}
void
TreeMutation::AfterInsertion(Accessible* aChild)
{
MOZ_ASSERT(aChild->Parent() == mParent);
if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
mStartIdx = aChild->mIndexInParent + 1;
}
if (!mQueueEvents) {
return;
}
RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
DebugOnly<bool> added = Controller()->QueueMutationEvent(ev);
MOZ_ASSERT(added);
aChild->SetShowEventTarget(true);
}
void
TreeMutation::BeforeRemoval(Accessible* aChild, bool aNoShutdown)
{
MOZ_ASSERT(aChild->Parent() == mParent);
if (static_cast<uint32_t>(aChild->mIndexInParent) < mStartIdx) {
mStartIdx = aChild->mIndexInParent;
}
if (!mQueueEvents) {
return;
}
RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, !aNoShutdown);
if (Controller()->QueueMutationEvent(ev)) {
aChild->SetHideEventTarget(true);
}
}
void
TreeMutation::Done()
{
MOZ_ASSERT(mParent->mStateFlags & Accessible::eKidsMutating);
mParent->mStateFlags &= ~Accessible::eKidsMutating;
uint32_t length = mParent->mChildren.Length();
#ifdef DEBUG
for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) {
MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast<int32_t>(idx),
"Wrong index detected");
}
#endif
for (uint32_t idx = mStartIdx; idx < length; idx++) {
mParent->mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty;
}
mParent->mEmbeddedObjCollector = nullptr;
mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
#ifdef DEBUG
mIsDone = true;
#endif
#ifdef A11Y_LOG
if (mQueueEvents && logging::IsEnabled(logging::eEventTree)) {
logging::MsgBegin("EVENTS_TREE", "reordering tree after");
logging::AccessibleInfo("reordering for", mParent);
Controller()->RootEventTree().Log();
logging::MsgEnd();
}
#endif
}
#ifdef A11Y_LOG
const char*
TreeMutation::PrefixLog(void* aData, Accessible* aAcc)
{
TreeMutation* thisObj = reinterpret_cast<TreeMutation*>(aData);
if (thisObj->mParent == aAcc) {
return "_X_";
}
const EventTree& ret = thisObj->Controller()->RootEventTree();
if (ret.Find(aAcc)) {
return "_с_";
}
return "";
}
#endif
////////////////////////////////////////////////////////////////////////////////
// EventTree
void
EventTree::Shown(Accessible* aChild)
{
RefPtr<AccShowEvent> ev = new AccShowEvent(aChild);
Controller(aChild)->WithdrawPrecedingEvents(&ev->mPrecedingEvents);
Mutated(ev);
}
void
EventTree::Hidden(Accessible* aChild, bool aNeedsShutdown)
{
RefPtr<AccHideEvent> ev = new AccHideEvent(aChild, aNeedsShutdown);
if (!aNeedsShutdown) {
Controller(aChild)->StorePrecedingEvent(ev);
}
Mutated(ev);
}
void
EventTree::Process(const RefPtr<DocAccessible>& aDeathGrip)
{
while (mFirst) {
// Skip a node and its subtree if its container is not in the document.
if (mFirst->mContainer->IsInDocument()) {
mFirst->Process(aDeathGrip);
if (aDeathGrip->IsDefunct()) {
return;
}
}
mFirst = Move(mFirst->mNext);
}
MOZ_ASSERT(mContainer || mDependentEvents.IsEmpty(),
"No container, no events");
MOZ_ASSERT(!mContainer || !mContainer->IsDefunct(),
"Processing events for defunct container");
MOZ_ASSERT(!mFireReorder || mContainer, "No target for reorder event");
// Fire mutation events.
uint32_t eventsCount = mDependentEvents.Length();
for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
AccMutationEvent* mtEvent = mDependentEvents[jdx];
MOZ_ASSERT(mtEvent->Document(), "No document for event target");
// Fire all hide events that has to be fired before this show event.
if (mtEvent->IsShow()) {
AccShowEvent* showEv = downcast_accEvent(mtEvent);
for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
nsEventShell::FireEvent(showEv->mPrecedingEvents[i]);
if (aDeathGrip->IsDefunct()) {
return;
}
}
}
nsEventShell::FireEvent(mtEvent);
if (aDeathGrip->IsDefunct()) {
return;
}
if (mtEvent->mTextChangeEvent) {
nsEventShell::FireEvent(mtEvent->mTextChangeEvent);
if (aDeathGrip->IsDefunct()) {
return;
}
}
if (mtEvent->IsHide()) {
// Fire menupopup end event before a hide event if a menu goes away.
// XXX: We don't look into children of hidden subtree to find hiding
// menupopup (as we did prior bug 570275) because we don't do that when
// menu is showing (and that's impossible until bug 606924 is fixed).
// Nevertheless we should do this at least because layout coalesces
// the changes before our processing and we may miss some menupopup
// events. Now we just want to be consistent in content insertion/removal
// handling.
if (mtEvent->mAccessible->ARIARole() == roles::MENUPOPUP) {
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
mtEvent->mAccessible);
if (aDeathGrip->IsDefunct()) {
return;
}
}
AccHideEvent* hideEvent = downcast_accEvent(mtEvent);
if (hideEvent->NeedsShutdown()) {
aDeathGrip->ShutdownChildrenInSubtree(mtEvent->mAccessible);
}
}
}
// Fire reorder event at last.
if (mFireReorder) {
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_REORDER, mContainer);
mContainer->Document()->MaybeNotifyOfValueChange(mContainer);
}
mDependentEvents.Clear();
}
EventTree*
EventTree::FindOrInsert(Accessible* aContainer)
{
if (!mFirst) {
mFirst.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
return mFirst.get();
}
EventTree* prevNode = nullptr;
EventTree* node = mFirst.get();
do {
MOZ_ASSERT(!node->mContainer->IsApplication(),
"No event for application accessible is expected here");
MOZ_ASSERT(!node->mContainer->IsDefunct(), "An event target has to be alive");
// Case of same target.
if (node->mContainer == aContainer) {
return node;
}
// Check if the given container is contained by a current node
Accessible* top = mContainer ? mContainer : aContainer->Document();
Accessible* parent = aContainer;
while (parent) {
// Reached a top, no match for a current event.
if (parent == top) {
break;
}
// We got a match.
if (parent->Parent() == node->mContainer) {
// Reject the node if it's contained by a show/hide event target
uint32_t evCount = node->mDependentEvents.Length();
for (uint32_t idx = 0; idx < evCount; idx++) {
AccMutationEvent* ev = node->mDependentEvents[idx];
if (ev->GetAccessible() == parent) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eEventTree)) {
logging::MsgBegin("EVENTS_TREE",
"Rejecting node contained by show/hide");
logging::AccessibleInfo("Node", aContainer);
logging::MsgEnd();
}
#endif
// If the node is rejected, then check if it has related hide event
// on stack, and if so, then connect it to the parent show event.
if (ev->IsShow()) {
AccShowEvent* showEv = downcast_accEvent(ev);
Controller(aContainer)->
WithdrawPrecedingEvents(&showEv->mPrecedingEvents);
}
return nullptr;
}
}
return node->FindOrInsert(aContainer);
}
parent = parent->Parent();
MOZ_ASSERT(parent, "Wrong tree");
}
// If the given container contains a current node
// then
// if show or hide of the given node contains a grand parent of the current node
// then ignore the current node and its show and hide events
// otherwise ignore the current node, but not its show and hide events
Accessible* curParent = node->mContainer;
while (curParent && !curParent->IsDoc()) {
if (curParent->Parent() != aContainer) {
curParent = curParent->Parent();
continue;
}
// Insert the tail node into the hierarchy between the current node and
// its parent.
node->mFireReorder = false;
UniquePtr<EventTree>& nodeOwnerRef = prevNode ? prevNode->mNext : mFirst;
UniquePtr<EventTree> newNode(new EventTree(aContainer, mDependentEvents.IsEmpty()));
newNode->mFirst = Move(nodeOwnerRef);
nodeOwnerRef = Move(newNode);
nodeOwnerRef->mNext = Move(node->mNext);
// Check if a next node is contained by the given node too, and move them
// under the given node if so.
prevNode = nodeOwnerRef.get();
node = nodeOwnerRef->mNext.get();
UniquePtr<EventTree>* nodeRef = &nodeOwnerRef->mNext;
EventTree* insNode = nodeOwnerRef->mFirst.get();
while (node) {
Accessible* curParent = node->mContainer;
while (curParent && !curParent->IsDoc()) {
if (curParent->Parent() != aContainer) {
curParent = curParent->Parent();
continue;
}
MOZ_ASSERT(!insNode->mNext);
node->mFireReorder = false;
insNode->mNext = Move(*nodeRef);
insNode = insNode->mNext.get();
prevNode->mNext = Move(node->mNext);
node = prevNode;
break;
}
prevNode = node;
nodeRef = &node->mNext;
node = node->mNext.get();
}
return nodeOwnerRef.get();
}
prevNode = node;
} while ((node = node->mNext.get()));
MOZ_ASSERT(prevNode, "Nowhere to insert");
MOZ_ASSERT(!prevNode->mNext, "Taken by another node");
// If 'this' node contains the given container accessible, then
// do not emit a reorder event for the container
// if a dependent show event target contains the given container then do not
// emit show / hide events (see Process() method)
prevNode->mNext.reset(new EventTree(aContainer, mDependentEvents.IsEmpty()));
return prevNode->mNext.get();
}
void
EventTree::Clear()
{
mFirst = nullptr;
mNext = nullptr;
mContainer = nullptr;
uint32_t eventsCount = mDependentEvents.Length();
for (uint32_t jdx = 0; jdx < eventsCount; jdx++) {
mDependentEvents[jdx]->mEventType = AccEvent::eDoNotEmit;
AccHideEvent* ev = downcast_accEvent(mDependentEvents[jdx]);
if (ev && ev->NeedsShutdown()) {
ev->Document()->ShutdownChildrenInSubtree(ev->mAccessible);
}
}
mDependentEvents.Clear();
}
const EventTree*
EventTree::Find(const Accessible* aContainer) const
{
const EventTree* et = this;
while (et) {
if (et->mContainer == aContainer) {
return et;
}
if (et->mFirst) {
et = et->mFirst.get();
const EventTree* cet = et->Find(aContainer);
if (cet) {
return cet;
}
}
et = et->mNext.get();
const EventTree* cet = et->Find(aContainer);
if (cet) {
return cet;
}
}
return nullptr;
}
#ifdef A11Y_LOG
void
EventTree::Log(uint32_t aLevel) const
{
if (aLevel == UINT32_MAX) {
if (mFirst) {
mFirst->Log(0);
}
return;
}
for (uint32_t i = 0; i < aLevel; i++) {
printf(" ");
}
logging::AccessibleInfo("container", mContainer);
for (uint32_t i = 0; i < mDependentEvents.Length(); i++) {
AccMutationEvent* ev = mDependentEvents[i];
if (ev->IsShow()) {
for (uint32_t i = 0; i < aLevel + 1; i++) {
printf(" ");
}
logging::AccessibleInfo("shown", ev->mAccessible);
AccShowEvent* showEv = downcast_accEvent(ev);
for (uint32_t i = 0; i < showEv->mPrecedingEvents.Length(); i++) {
for (uint32_t j = 0; j < aLevel + 1; j++) {
printf(" ");
}
logging::AccessibleInfo("preceding",
showEv->mPrecedingEvents[i]->mAccessible);
}
}
else {
for (uint32_t i = 0; i < aLevel + 1; i++) {
printf(" ");
}
logging::AccessibleInfo("hidden", ev->mAccessible);
}
}
if (mFirst) {
mFirst->Log(aLevel + 1);
}
if (mNext) {
mNext->Log(aLevel);
}
}
#endif
void
EventTree::Mutated(AccMutationEvent* aEv)
{
// If shown or hidden node is a root of previously mutated subtree, then
// discard those subtree mutations as we are no longer interested in them.
UniquePtr<EventTree>* node = &mFirst;
while (*node) {
Accessible* cntr = (*node)->mContainer;
while (cntr != mContainer) {
if (cntr == aEv->mAccessible) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eEventTree)) {
logging::MsgBegin("EVENTS_TREE", "Trim subtree");
logging::AccessibleInfo("Show/hide container", aEv->mAccessible);
logging::AccessibleInfo("Trimmed subtree root", (*node)->mContainer);
logging::MsgEnd();
}
#endif
// If the new hide is part of a move and it contains existing child
// shows, then move preceding events from the child shows to the buffer,
// so the ongoing show event will pick them up.
if (aEv->IsHide()) {
AccHideEvent* hideEv = downcast_accEvent(aEv);
if (!hideEv->mNeedsShutdown) {
for (uint32_t i = 0; i < (*node)->mDependentEvents.Length(); i++) {
AccMutationEvent* childEv = (*node)->mDependentEvents[i];
if (childEv->IsShow()) {
AccShowEvent* childShowEv = downcast_accEvent(childEv);
if (childShowEv->mPrecedingEvents.Length() > 0) {
Controller(mContainer)->StorePrecedingEvents(
mozilla::Move(childShowEv->mPrecedingEvents));
}
}
}
}
}
// If the new show contains existing child shows, then move preceding
// events from the child shows to the new show.
else if (aEv->IsShow()) {
AccShowEvent* showEv = downcast_accEvent(aEv);
for (uint32_t i = 0; (*node)->mDependentEvents.Length(); i++) {
AccMutationEvent* childEv = (*node)->mDependentEvents[i];
if (childEv->IsShow()) {
AccShowEvent* showChildEv = downcast_accEvent(childEv);
if (showChildEv->mPrecedingEvents.Length() > 0) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eEventTree)) {
logging::MsgBegin("EVENTS_TREE", "Adopt preceding events");
logging::AccessibleInfo("Parent", aEv->mAccessible);
for (uint32_t j = 0; j < showChildEv->mPrecedingEvents.Length(); j++) {
logging::AccessibleInfo("Adoptee",
showChildEv->mPrecedingEvents[i]->mAccessible);
}
logging::MsgEnd();
}
#endif
showEv->mPrecedingEvents.AppendElements(showChildEv->mPrecedingEvents);
}
}
}
}
*node = Move((*node)->mNext);
break;
}
cntr = cntr->Parent();
}
if (cntr == aEv->mAccessible) {
continue;
}
node = &(*node)->mNext;
}
AccMutationEvent* prevEvent = mDependentEvents.SafeLastElement(nullptr);
mDependentEvents.AppendElement(aEv);
// Coalesce text change events from this hide/show event and the previous one.
if (prevEvent && aEv->mEventType == prevEvent->mEventType) {
if (aEv->IsHide()) {
// XXX: we need a way to ignore SplitNode and JoinNode() when they do not
// affect the text within the hypertext.
AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
if (prevTextEvent) {
AccHideEvent* hideEvent = downcast_accEvent(aEv);
AccHideEvent* prevHideEvent = downcast_accEvent(prevEvent);
if (prevHideEvent->mNextSibling == hideEvent->mAccessible) {
hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
}
else if (prevHideEvent->mPrevSibling == hideEvent->mAccessible) {
uint32_t oldLen = prevTextEvent->GetLength();
hideEvent->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
prevTextEvent->mStart -= prevTextEvent->GetLength() - oldLen;
}
hideEvent->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
}
}
else {
AccTextChangeEvent* prevTextEvent = prevEvent->mTextChangeEvent;
if (prevTextEvent) {
if (aEv->mAccessible->IndexInParent() ==
prevEvent->mAccessible->IndexInParent() + 1) {
// If tail target was inserted after this target, i.e. tail target is next
// sibling of this target.
aEv->mAccessible->AppendTextTo(prevTextEvent->mModifiedText);
}
else if (aEv->mAccessible->IndexInParent() ==
prevEvent->mAccessible->IndexInParent() - 1) {
// If tail target was inserted before this target, i.e. tail target is
// previous sibling of this target.
nsAutoString startText;
aEv->mAccessible->AppendTextTo(startText);
prevTextEvent->mModifiedText = startText + prevTextEvent->mModifiedText;
prevTextEvent->mStart -= startText.Length();
}
aEv->mTextChangeEvent.swap(prevEvent->mTextChangeEvent);
}
}
}
// Create a text change event caused by this hide/show event. When a node is
// hidden/removed or shown/appended, the text in an ancestor hyper text will
// lose or get new characters.
if (aEv->mTextChangeEvent || !mContainer->IsHyperText()) {
return;
}
nsAutoString text;
aEv->mAccessible->AppendTextTo(text);
if (text.IsEmpty()) {
return;
}
int32_t offset = mContainer->AsHyperText()->GetChildOffset(aEv->mAccessible);
aEv->mTextChangeEvent =
new AccTextChangeEvent(mContainer, offset, text, aEv->IsShow(),
aEv->mIsFromUserInput ? eFromUserInput : eNoUserInput);
}

121
accessible/base/EventTree.h Normal file
View file

@ -0,0 +1,121 @@
/* -*- 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/. */
#ifndef mozilla_a11y_EventTree_h_
#define mozilla_a11y_EventTree_h_
#include "AccEvent.h"
#include "Accessible.h"
#include "mozilla/RefPtr.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
namespace a11y {
/**
* This class makes sure required tasks are done before and after tree
* mutations. Currently this only includes group info invalidation. You must
* have an object of this class on the stack when calling methods that mutate
* the accessible tree.
*/
class TreeMutation final
{
public:
static const bool kNoEvents = true;
static const bool kNoShutdown = true;
explicit TreeMutation(Accessible* aParent, bool aNoEvents = false);
~TreeMutation();
void AfterInsertion(Accessible* aChild);
void BeforeRemoval(Accessible* aChild, bool aNoShutdown = false);
void Done();
private:
NotificationController* Controller() const
{ return mParent->Document()->Controller(); }
static EventTree* const kNoEventTree;
#ifdef A11Y_LOG
static const char* PrefixLog(void* aData, Accessible*);
#endif
Accessible* mParent;
uint32_t mStartIdx;
uint32_t mStateFlagsCopy;
/*
* True if mutation events should be queued.
*/
bool mQueueEvents;
#ifdef DEBUG
bool mIsDone;
#endif
};
/**
* A mutation events coalescence structure.
*/
class EventTree final {
public:
EventTree() :
mFirst(nullptr), mNext(nullptr), mContainer(nullptr), mFireReorder(false) { }
explicit EventTree(Accessible* aContainer, bool aFireReorder) :
mFirst(nullptr), mNext(nullptr), mContainer(aContainer),
mFireReorder(aFireReorder) { }
~EventTree() { Clear(); }
void Shown(Accessible* aTarget);
void Hidden(Accessible*, bool);
/**
* Return an event tree node for the given accessible.
*/
const EventTree* Find(const Accessible* aContainer) const;
/**
* Add a mutation event to this event tree.
*/
void Mutated(AccMutationEvent* aEv);
#ifdef A11Y_LOG
void Log(uint32_t aLevel = UINT32_MAX) const;
#endif
private:
/**
* Processes the event queue and fires events.
*/
void Process(const RefPtr<DocAccessible>& aDeathGrip);
/**
* Return an event subtree for the given accessible.
*/
EventTree* FindOrInsert(Accessible* aContainer);
void Clear();
UniquePtr<EventTree> mFirst;
UniquePtr<EventTree> mNext;
Accessible* mContainer;
nsTArray<RefPtr<AccMutationEvent>> mDependentEvents;
bool mFireReorder;
static NotificationController* Controller(Accessible* aAcc)
{ return aAcc->Document()->Controller(); }
friend class NotificationController;
};
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11y_EventQueue_h_

View file

@ -0,0 +1,51 @@
/* 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 "Filters.h"
#include "Accessible-inl.h"
#include "nsAccUtils.h"
#include "Role.h"
#include "States.h"
using namespace mozilla::a11y;
using namespace mozilla::a11y::filters;
uint32_t
filters::GetSelected(Accessible* aAccessible)
{
if (aAccessible->State() & states::SELECTED)
return eMatch | eSkipSubtree;
return eSkip;
}
uint32_t
filters::GetSelectable(Accessible* aAccessible)
{
if (aAccessible->InteractiveState() & states::SELECTABLE)
return eMatch | eSkipSubtree;
return eSkip;
}
uint32_t
filters::GetRow(Accessible* aAccessible)
{
a11y::role role = aAccessible->Role();
if (role == roles::ROW)
return eMatch | eSkipSubtree;
// Look for rows inside rowgroup.
if (role == roles::GROUPING)
return eSkip;
return eSkipSubtree;
}
uint32_t
filters::GetCell(Accessible* aAccessible)
{
return aAccessible->IsTableCell() ? eMatch : eSkipSubtree;
}

50
accessible/base/Filters.h Normal file
View file

@ -0,0 +1,50 @@
/* 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/. */
#ifndef mozilla_a11y_Filters_h__
#define mozilla_a11y_Filters_h__
#include <stdint.h>
/**
* Predefined filters used for nsAccIterator and nsAccCollector.
*/
namespace mozilla {
namespace a11y {
class Accessible;
namespace filters {
enum EResult {
eSkip = 0,
eMatch = 1,
eSkipSubtree = 2
};
/**
* Return true if the traversed accessible complies with filter.
*/
typedef uint32_t (*FilterFuncPtr) (Accessible*);
/**
* Matches selected/selectable accessibles in subtree.
*/
uint32_t GetSelected(Accessible* aAccessible);
uint32_t GetSelectable(Accessible* aAccessible);
/**
* Matches row accessibles in subtree.
*/
uint32_t GetRow(Accessible* aAccessible);
/**
* Matches cell accessibles in children.
*/
uint32_t GetCell(Accessible* aAccessible);
} // namespace filters
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,404 @@
/* 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 "FocusManager.h"
#include "Accessible-inl.h"
#include "AccIterator.h"
#include "DocAccessible-inl.h"
#include "nsAccessibilityService.h"
#include "nsAccUtils.h"
#include "nsEventShell.h"
#include "Role.h"
#include "nsFocusManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/dom/Element.h"
namespace mozilla {
namespace a11y {
FocusManager::FocusManager()
{
}
FocusManager::~FocusManager()
{
}
Accessible*
FocusManager::FocusedAccessible() const
{
if (mActiveItem)
return mActiveItem;
nsINode* focusedNode = FocusedDOMNode();
if (focusedNode) {
DocAccessible* doc =
GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
return doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr;
}
return nullptr;
}
bool
FocusManager::IsFocused(const Accessible* aAccessible) const
{
if (mActiveItem)
return mActiveItem == aAccessible;
nsINode* focusedNode = FocusedDOMNode();
if (focusedNode) {
// XXX: Before getting an accessible for node having a DOM focus make sure
// they belong to the same document because it can trigger unwanted document
// accessible creation for temporary about:blank document. Without this
// peculiarity we would end up with plain implementation based on
// FocusedAccessible() method call. Make sure this issue is fixed in
// bug 638465.
if (focusedNode->OwnerDoc() == aAccessible->GetNode()->OwnerDoc()) {
DocAccessible* doc =
GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
return aAccessible ==
(doc ? doc->GetAccessibleEvenIfNotInMapOrContainer(focusedNode) : nullptr);
}
}
return false;
}
bool
FocusManager::IsFocusWithin(const Accessible* aContainer) const
{
Accessible* child = FocusedAccessible();
while (child) {
if (child == aContainer)
return true;
child = child->Parent();
}
return false;
}
FocusManager::FocusDisposition
FocusManager::IsInOrContainsFocus(const Accessible* aAccessible) const
{
Accessible* focus = FocusedAccessible();
if (!focus)
return eNone;
// If focused.
if (focus == aAccessible)
return eFocused;
// If contains the focus.
Accessible* child = focus->Parent();
while (child) {
if (child == aAccessible)
return eContainsFocus;
child = child->Parent();
}
// If contained by focus.
child = aAccessible->Parent();
while (child) {
if (child == focus)
return eContainedByFocus;
child = child->Parent();
}
return eNone;
}
void
FocusManager::NotifyOfDOMFocus(nsISupports* aTarget)
{
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
logging::FocusNotificationTarget("DOM focus", "Target", aTarget);
#endif
mActiveItem = nullptr;
nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
if (targetNode) {
DocAccessible* document =
GetAccService()->GetDocAccessible(targetNode->OwnerDoc());
if (document) {
// Set selection listener for focused element.
if (targetNode->IsElement())
SelectionMgr()->SetControlSelectionListener(targetNode->AsElement());
document->HandleNotification<FocusManager, nsINode>
(this, &FocusManager::ProcessDOMFocus, targetNode);
}
}
}
void
FocusManager::NotifyOfDOMBlur(nsISupports* aTarget)
{
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
logging::FocusNotificationTarget("DOM blur", "Target", aTarget);
#endif
mActiveItem = nullptr;
// If DOM document stays focused then fire accessible focus event to process
// the case when no element within this DOM document will be focused.
nsCOMPtr<nsINode> targetNode(do_QueryInterface(aTarget));
if (targetNode && targetNode->OwnerDoc() == FocusedDOMDocument()) {
nsIDocument* DOMDoc = targetNode->OwnerDoc();
DocAccessible* document =
GetAccService()->GetDocAccessible(DOMDoc);
if (document) {
// Clear selection listener for previously focused element.
if (targetNode->IsElement())
SelectionMgr()->ClearControlSelectionListener();
document->HandleNotification<FocusManager, nsINode>
(this, &FocusManager::ProcessDOMFocus, DOMDoc);
}
}
}
void
FocusManager::ActiveItemChanged(Accessible* aItem, bool aCheckIfActive)
{
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
logging::FocusNotificationTarget("active item changed", "Item", aItem);
#endif
// Nothing changed, happens for XUL trees and HTML selects.
if (aItem && aItem == mActiveItem)
return;
mActiveItem = nullptr;
if (aItem && aCheckIfActive) {
Accessible* widget = aItem->ContainerWidget();
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
logging::ActiveWidget(widget);
#endif
if (!widget || !widget->IsActiveWidget() || !widget->AreItemsOperable())
return;
}
mActiveItem = aItem;
// If active item is changed then fire accessible focus event on it, otherwise
// if there's no an active item then fire focus event to accessible having
// DOM focus.
Accessible* target = FocusedAccessible();
if (target)
DispatchFocusEvent(target->Document(), target);
}
void
FocusManager::ForceFocusEvent()
{
nsINode* focusedNode = FocusedDOMNode();
if (focusedNode) {
DocAccessible* document =
GetAccService()->GetDocAccessible(focusedNode->OwnerDoc());
if (document) {
document->HandleNotification<FocusManager, nsINode>
(this, &FocusManager::ProcessDOMFocus, focusedNode);
}
}
}
void
FocusManager::DispatchFocusEvent(DocAccessible* aDocument,
Accessible* aTarget)
{
NS_PRECONDITION(aDocument, "No document for focused accessible!");
if (aDocument) {
RefPtr<AccEvent> event =
new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, aTarget,
eAutoDetect, AccEvent::eCoalesceOfSameType);
aDocument->FireDelayedEvent(event);
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
logging::FocusDispatched(aTarget);
#endif
}
}
void
FocusManager::ProcessDOMFocus(nsINode* aTarget)
{
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
logging::FocusNotificationTarget("process DOM focus", "Target", aTarget);
#endif
DocAccessible* document =
GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
if (!document)
return;
Accessible* target = document->GetAccessibleEvenIfNotInMapOrContainer(aTarget);
if (target) {
// Check if still focused. Otherwise we can end up with storing the active
// item for control that isn't focused anymore.
nsINode* focusedNode = FocusedDOMNode();
if (!focusedNode)
return;
Accessible* DOMFocus =
document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
if (target != DOMFocus)
return;
Accessible* activeItem = target->CurrentItem();
if (activeItem) {
mActiveItem = activeItem;
target = activeItem;
}
DispatchFocusEvent(document, target);
}
}
void
FocusManager::ProcessFocusEvent(AccEvent* aEvent)
{
NS_PRECONDITION(aEvent->GetEventType() == nsIAccessibleEvent::EVENT_FOCUS,
"Focus event is expected!");
// Emit focus event if event target is the active item. Otherwise then check
// if it's still focused and then update active item and emit focus event.
Accessible* target = aEvent->GetAccessible();
if (target != mActiveItem) {
// Check if still focused. Otherwise we can end up with storing the active
// item for control that isn't focused anymore.
DocAccessible* document = aEvent->Document();
nsINode* focusedNode = FocusedDOMNode();
if (!focusedNode)
return;
Accessible* DOMFocus =
document->GetAccessibleEvenIfNotInMapOrContainer(focusedNode);
if (target != DOMFocus)
return;
Accessible* activeItem = target->CurrentItem();
if (activeItem) {
mActiveItem = activeItem;
target = activeItem;
}
}
// Fire menu start/end events for ARIA menus.
if (target->IsARIARole(nsGkAtoms::menuitem)) {
// The focus was moved into menu.
Accessible* ARIAMenubar = nullptr;
for (Accessible* parent = target->Parent(); parent; parent = parent->Parent()) {
if (parent->IsARIARole(nsGkAtoms::menubar)) {
ARIAMenubar = parent;
break;
}
// Go up in the parent chain of the menu hierarchy.
if (!parent->IsARIARole(nsGkAtoms::menuitem) &&
!parent->IsARIARole(nsGkAtoms::menu)) {
break;
}
}
if (ARIAMenubar != mActiveARIAMenubar) {
// Leaving ARIA menu. Fire menu_end event on current menubar.
if (mActiveARIAMenubar) {
RefPtr<AccEvent> menuEndEvent =
new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
aEvent->FromUserInput());
nsEventShell::FireEvent(menuEndEvent);
}
mActiveARIAMenubar = ARIAMenubar;
// Entering ARIA menu. Fire menu_start event.
if (mActiveARIAMenubar) {
RefPtr<AccEvent> menuStartEvent =
new AccEvent(nsIAccessibleEvent::EVENT_MENU_START,
mActiveARIAMenubar, aEvent->FromUserInput());
nsEventShell::FireEvent(menuStartEvent);
}
}
} else if (mActiveARIAMenubar) {
// Focus left a menu. Fire menu_end event.
RefPtr<AccEvent> menuEndEvent =
new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mActiveARIAMenubar,
aEvent->FromUserInput());
nsEventShell::FireEvent(menuEndEvent);
mActiveARIAMenubar = nullptr;
}
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
logging::FocusNotificationTarget("fire focus event", "Target", target);
#endif
// Reset cached caret value. The cache will be updated upon processing the
// next caret move event. This ensures that we will return the correct caret
// offset before the caret move event is handled.
SelectionMgr()->ResetCaretOffset();
RefPtr<AccEvent> focusEvent =
new AccEvent(nsIAccessibleEvent::EVENT_FOCUS, target, aEvent->FromUserInput());
nsEventShell::FireEvent(focusEvent);
// Fire scrolling_start event when the document receives the focus if it has
// an anchor jump. If an accessible within the document receive the focus
// then null out the anchor jump because it no longer applies.
DocAccessible* targetDocument = target->Document();
Accessible* anchorJump = targetDocument->AnchorJump();
if (anchorJump) {
if (target == targetDocument) {
// XXX: bug 625699, note in some cases the node could go away before we
// we receive focus event, for example if the node is removed from DOM.
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_START,
anchorJump, aEvent->FromUserInput());
}
targetDocument->SetAnchorJump(nullptr);
}
}
nsINode*
FocusManager::FocusedDOMNode() const
{
nsFocusManager* DOMFocusManager = nsFocusManager::GetFocusManager();
nsIContent* focusedElm = DOMFocusManager->GetFocusedContent();
// No focus on remote target elements like xul:browser having DOM focus and
// residing in chrome process because it means an element in content process
// keeps the focus.
if (focusedElm) {
if (EventStateManager::IsRemoteTarget(focusedElm)) {
return nullptr;
}
return focusedElm;
}
// Otherwise the focus can be on DOM document.
nsPIDOMWindowOuter* focusedWnd = DOMFocusManager->GetFocusedWindow();
return focusedWnd ? focusedWnd->GetExtantDoc() : nullptr;
}
nsIDocument*
FocusManager::FocusedDOMDocument() const
{
nsINode* focusedNode = FocusedDOMNode();
return focusedNode ? focusedNode->OwnerDoc() : nullptr;
}
} // namespace a11y
} // namespace mozilla

View file

@ -0,0 +1,132 @@
/* 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/. */
#ifndef mozilla_a11y_FocusManager_h_
#define mozilla_a11y_FocusManager_h_
class nsINode;
class nsIDocument;
class nsISupports;
namespace mozilla {
namespace a11y {
class AccEvent;
class Accessible;
class DocAccessible;
/**
* Manage the accessible focus. Used to fire and process accessible events.
*/
class FocusManager
{
public:
virtual ~FocusManager();
/**
* Return a focused accessible.
*/
Accessible* FocusedAccessible() const;
/**
* Return true if given accessible is focused.
*/
bool IsFocused(const Accessible* aAccessible) const;
/**
* Return true if the given accessible is an active item, i.e. an item that
* is current within the active widget.
*/
inline bool IsActiveItem(const Accessible* aAccessible)
{ return aAccessible == mActiveItem; }
/**
* Return true if given DOM node has DOM focus.
*/
inline bool HasDOMFocus(const nsINode* aNode) const
{ return aNode == FocusedDOMNode(); }
/**
* Return true if focused accessible is within the given container.
*/
bool IsFocusWithin(const Accessible* aContainer) const;
/**
* Return whether the given accessible is focused or contains the focus or
* contained by focused accessible.
*/
enum FocusDisposition {
eNone,
eFocused,
eContainsFocus,
eContainedByFocus
};
FocusDisposition IsInOrContainsFocus(const Accessible* aAccessible) const;
//////////////////////////////////////////////////////////////////////////////
// Focus notifications and processing (don't use until you know what you do).
/**
* Called when DOM focus event is fired.
*/
void NotifyOfDOMFocus(nsISupports* aTarget);
/**
* Called when DOM blur event is fired.
*/
void NotifyOfDOMBlur(nsISupports* aTarget);
/**
* Called when active item is changed. Note: must be called when accessible
* tree is up to date.
*/
void ActiveItemChanged(Accessible* aItem, bool aCheckIfActive = true);
/**
* Dispatch delayed focus event for the current focus accessible.
*/
void ForceFocusEvent();
/**
* Dispatch delayed focus event for the given target.
*/
void DispatchFocusEvent(DocAccessible* aDocument, Accessible* aTarget);
/**
* Process DOM focus notification.
*/
void ProcessDOMFocus(nsINode* aTarget);
/**
* Process the delayed accessible event.
* do.
*/
void ProcessFocusEvent(AccEvent* aEvent);
protected:
FocusManager();
private:
FocusManager(const FocusManager&);
FocusManager& operator =(const FocusManager&);
/**
* Return DOM node having DOM focus.
*/
nsINode* FocusedDOMNode() const;
/**
* Return DOM document having DOM focus.
*/
nsIDocument* FocusedDOMDocument() const;
private:
RefPtr<Accessible> mActiveItem;
RefPtr<Accessible> mActiveARIAMenubar;
};
} // namespace a11y
} // namespace mozilla
#endif

1039
accessible/base/Logging.cpp Normal file

File diff suppressed because it is too large Load diff

225
accessible/base/Logging.h Normal file
View file

@ -0,0 +1,225 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_logs_h__
#define mozilla_a11y_logs_h__
#include "nscore.h"
#include "nsStringFwd.h"
class nsIDocument;
class nsINode;
class nsIRequest;
class nsISelection;
class nsISupports;
class nsIWebProgress;
namespace mozilla {
namespace a11y {
class AccEvent;
class Accessible;
class DocAccessible;
class OuterDocAccessible;
namespace logging {
enum EModules {
eDocLoad = 1 << 0,
eDocCreate = 1 << 1,
eDocDestroy = 1 << 2,
eDocLifeCycle = eDocLoad | eDocCreate | eDocDestroy,
eEvents = 1 << 3,
eEventTree = 1 << 4,
ePlatforms = 1 << 5,
eText = 1 << 6,
eTree = 1 << 7,
eDOMEvents = 1 << 8,
eFocus = 1 << 9,
eSelection = 1 << 10,
eNotifications = eDOMEvents | eSelection | eFocus,
// extras
eStack = 1 << 11,
eVerbose = 1 << 12
};
/**
* Return true if any of the given modules is logged.
*/
bool IsEnabled(uint32_t aModules);
/**
* Return true if all of the given modules are logged.
*/
bool IsEnabledAll(uint32_t aModules);
/**
* Return true if the given module is logged.
*/
bool IsEnabled(const nsAString& aModules);
/**
* Log the document loading progress.
*/
void DocLoad(const char* aMsg, nsIWebProgress* aWebProgress,
nsIRequest* aRequest, uint32_t aStateFlags);
void DocLoad(const char* aMsg, nsIDocument* aDocumentNode);
void DocCompleteLoad(DocAccessible* aDocument, bool aIsLoadEventTarget);
/**
* Log that document load event was fired.
*/
void DocLoadEventFired(AccEvent* aEvent);
/**
* Log that document laod event was handled.
*/
void DocLoadEventHandled(AccEvent* aEvent);
/**
* Log the document was created.
*/
void DocCreate(const char* aMsg, nsIDocument* aDocumentNode,
DocAccessible* aDocument = nullptr);
/**
* Log the document was destroyed.
*/
void DocDestroy(const char* aMsg, nsIDocument* aDocumentNode,
DocAccessible* aDocument = nullptr);
/**
* Log the outer document was destroyed.
*/
void OuterDocDestroy(OuterDocAccessible* OuterDoc);
/**
* Log the focus notification target.
*/
void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
Accessible* aTarget);
void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
nsINode* aTargetNode);
void FocusNotificationTarget(const char* aMsg, const char* aTargetDescr,
nsISupports* aTargetThing);
/**
* Log a cause of active item descendant change (submessage).
*/
void ActiveItemChangeCausedBy(const char* aMsg, Accessible* aTarget);
/**
* Log the active widget (submessage).
*/
void ActiveWidget(Accessible* aWidget);
/**
* Log the focus event was dispatched (submessage).
*/
void FocusDispatched(Accessible* aTarget);
/**
* Log the selection change.
*/
void SelChange(nsISelection* aSelection, DocAccessible* aDocument,
int16_t aReason);
/**
* Log the given accessible elements info.
*/
void TreeInfo(const char* aMsg, uint32_t aExtraFlags, ...);
void TreeInfo(const char* aMsg, uint32_t aExtraFlags,
const char* aMsg1, Accessible* aAcc,
const char* aMsg2, nsINode* aNode);
void TreeInfo(const char* aMsg, uint32_t aExtraFlags, Accessible* aParent);
/**
* Log the accessible/DOM tree.
*/
typedef const char* (*GetTreePrefix)(void* aData, Accessible*);
void Tree(const char* aTitle, const char* aMsgText, Accessible* aRoot,
GetTreePrefix aPrefixFunc = nullptr, void* aGetTreePrefixData = nullptr);
void DOMTree(const char* aTitle, const char* aMsgText, DocAccessible* aDoc);
/**
* Log the message ('title: text' format) on new line. Print the start and end
* boundaries of the message body designated by '{' and '}' (2 spaces indent for
* body).
*/
void MsgBegin(const char* aTitle, const char* aMsgText, ...);
void MsgEnd();
/**
* Print start and end boundaries of the message body designated by '{' and '}'
* (2 spaces indent for body).
*/
void SubMsgBegin();
void SubMsgEnd();
/**
* Log the entry into message body (4 spaces indent).
*/
void MsgEntry(const char* aEntryText, ...);
/**
* Log the text, two spaces offset is used.
*/
void Text(const char* aText);
/**
* Log the accessible object address as message entry (4 spaces indent).
*/
void Address(const char* aDescr, Accessible* aAcc);
/**
* Log the DOM node info as message entry.
*/
void Node(const char* aDescr, nsINode* aNode);
/**
* Log the document accessible info as message entry.
*/
void Document(DocAccessible* aDocument);
/**
* Log the accessible and its DOM node as a message entry.
*/
void AccessibleInfo(const char* aDescr, Accessible* aAccessible);
void AccessibleNNode(const char* aDescr, Accessible* aAccessible);
void AccessibleNNode(const char* aDescr, nsINode* aNode);
/**
* Log the DOM event.
*/
void DOMEvent(const char* aDescr, nsINode* aOrigTarget,
const nsAString& aEventType);
/**
* Log the call stack, two spaces offset is used.
*/
void Stack();
/**
* Enable logging of the specified modules, all other modules aren't logged.
*/
void Enable(const nsAFlatCString& aModules);
/**
* Enable logging of modules specified by A11YLOG environment variable,
* all other modules aren't logged.
*/
void CheckEnv();
} // namespace logging
} // namespace a11y
} // namespace mozilla
#endif

340
accessible/base/MarkupMap.h Normal file
View file

@ -0,0 +1,340 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=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/. */
MARKUPMAP(a,
New_HTMLLink,
roles::LINK)
MARKUPMAP(abbr,
New_HyperText,
0)
MARKUPMAP(acronym,
New_HyperText,
0)
MARKUPMAP(article,
New_HyperText,
roles::DOCUMENT,
Attr(xmlroles, article))
MARKUPMAP(aside,
New_HyperText,
roles::NOTE)
MARKUPMAP(blockquote,
New_HyperText,
roles::SECTION)
MARKUPMAP(dd,
New_HTMLDefinition,
roles::DEFINITION)
MARKUPMAP(details,
New_HyperText,
roles::DETAILS)
MARKUPMAP(div,
nullptr,
roles::SECTION)
MARKUPMAP(dl,
New_HTMLList,
roles::DEFINITION_LIST)
MARKUPMAP(dt,
New_HTMLListitem,
roles::TERM)
MARKUPMAP(figcaption,
New_HTMLFigcaption,
roles::CAPTION)
MARKUPMAP(figure,
New_HTMLFigure,
roles::FIGURE,
Attr(xmlroles, figure))
MARKUPMAP(form,
New_HyperText,
roles::FORM)
MARKUPMAP(footer,
New_HyperText,
roles::FOOTER)
MARKUPMAP(header,
New_HyperText,
roles::HEADER)
MARKUPMAP(h1,
New_HyperText,
roles::HEADING)
MARKUPMAP(h2,
New_HyperText,
roles::HEADING)
MARKUPMAP(h3,
New_HyperText,
roles::HEADING)
MARKUPMAP(h4,
New_HyperText,
roles::HEADING)
MARKUPMAP(h5,
New_HyperText,
roles::HEADING)
MARKUPMAP(h6,
New_HyperText,
roles::HEADING)
MARKUPMAP(label,
New_HTMLLabel,
roles::LABEL)
MARKUPMAP(legend,
New_HTMLLegend,
roles::LABEL)
MARKUPMAP(li,
New_HTMLListitem,
0)
MARKUPMAP(map,
nullptr,
roles::TEXT_CONTAINER)
MARKUPMAP(math,
New_HyperText,
roles::MATHML_MATH)
MARKUPMAP(mi_,
New_HyperText,
roles::MATHML_IDENTIFIER)
MARKUPMAP(mn_,
New_HyperText,
roles::MATHML_NUMBER)
MARKUPMAP(mo_,
New_HyperText,
roles::MATHML_OPERATOR,
AttrFromDOM(accent_, accent_),
AttrFromDOM(fence_, fence_),
AttrFromDOM(separator_, separator_),
AttrFromDOM(largeop_, largeop_))
MARKUPMAP(mtext_,
New_HyperText,
roles::MATHML_TEXT)
MARKUPMAP(ms_,
New_HyperText,
roles::MATHML_STRING_LITERAL)
MARKUPMAP(mglyph_,
New_HyperText,
roles::MATHML_GLYPH)
MARKUPMAP(mrow_,
New_HyperText,
roles::MATHML_ROW)
MARKUPMAP(mfrac_,
New_HyperText,
roles::MATHML_FRACTION,
AttrFromDOM(bevelled_, bevelled_),
AttrFromDOM(linethickness_, linethickness_))
MARKUPMAP(msqrt_,
New_HyperText,
roles::MATHML_SQUARE_ROOT)
MARKUPMAP(mroot_,
New_HyperText,
roles::MATHML_ROOT)
MARKUPMAP(mfenced_,
New_HyperText,
roles::MATHML_FENCED,
AttrFromDOM(close, close),
AttrFromDOM(open, open),
AttrFromDOM(separators_, separators_))
MARKUPMAP(menclose_,
New_HyperText,
roles::MATHML_ENCLOSED,
AttrFromDOM(notation_, notation_))
MARKUPMAP(mstyle_,
New_HyperText,
roles::MATHML_STYLE)
MARKUPMAP(msub_,
New_HyperText,
roles::MATHML_SUB)
MARKUPMAP(msup_,
New_HyperText,
roles::MATHML_SUP)
MARKUPMAP(msubsup_,
New_HyperText,
roles::MATHML_SUB_SUP)
MARKUPMAP(munder_,
New_HyperText,
roles::MATHML_UNDER,
AttrFromDOM(accentunder_, accentunder_),
AttrFromDOM(align, align))
MARKUPMAP(mover_,
New_HyperText,
roles::MATHML_OVER,
AttrFromDOM(accent_, accent_),
AttrFromDOM(align, align))
MARKUPMAP(munderover_,
New_HyperText,
roles::MATHML_UNDER_OVER,
AttrFromDOM(accent_, accent_),
AttrFromDOM(accentunder_, accentunder_),
AttrFromDOM(align, align))
MARKUPMAP(mmultiscripts_,
New_HyperText,
roles::MATHML_MULTISCRIPTS)
MARKUPMAP(mtable_,
New_HTMLTableAccessible,
roles::MATHML_TABLE,
AttrFromDOM(align, align),
AttrFromDOM(columnlines_, columnlines_),
AttrFromDOM(rowlines_, rowlines_))
MARKUPMAP(mlabeledtr_,
New_HTMLTableRowAccessible,
roles::MATHML_LABELED_ROW)
MARKUPMAP(mtr_,
New_HTMLTableRowAccessible,
roles::MATHML_TABLE_ROW)
MARKUPMAP(mtd_,
New_HTMLTableCellAccessible,
roles::MATHML_CELL)
MARKUPMAP(maction_,
New_HyperText,
roles::MATHML_ACTION,
AttrFromDOM(actiontype_, actiontype_),
AttrFromDOM(selection_, selection_))
MARKUPMAP(merror_,
New_HyperText,
roles::MATHML_ERROR)
MARKUPMAP(mstack_,
New_HyperText,
roles::MATHML_STACK,
AttrFromDOM(align, align),
AttrFromDOM(position, position))
MARKUPMAP(mlongdiv_,
New_HyperText,
roles::MATHML_LONG_DIVISION,
AttrFromDOM(longdivstyle_, longdivstyle_))
MARKUPMAP(msgroup_,
New_HyperText,
roles::MATHML_STACK_GROUP,
AttrFromDOM(position, position),
AttrFromDOM(shift_, shift_))
MARKUPMAP(msrow_,
New_HyperText,
roles::MATHML_STACK_ROW,
AttrFromDOM(position, position))
MARKUPMAP(mscarries_,
New_HyperText,
roles::MATHML_STACK_CARRIES,
AttrFromDOM(location_, location_),
AttrFromDOM(position, position))
MARKUPMAP(mscarry_,
New_HyperText,
roles::MATHML_STACK_CARRY,
AttrFromDOM(crossout_, crossout_))
MARKUPMAP(msline_,
New_HyperText,
roles::MATHML_STACK_LINE,
AttrFromDOM(position, position))
MARKUPMAP(nav,
New_HyperText,
roles::SECTION)
MARKUPMAP(ol,
New_HTMLList,
roles::LIST)
MARKUPMAP(option,
New_HTMLOption,
0)
MARKUPMAP(optgroup,
New_HTMLOptgroup,
0)
MARKUPMAP(output,
New_HTMLOutput,
roles::SECTION,
Attr(live, polite))
MARKUPMAP(p,
nullptr,
roles::PARAGRAPH)
MARKUPMAP(progress,
New_HTMLProgress,
0)
MARKUPMAP(q,
New_HyperText,
0)
MARKUPMAP(section,
New_HyperText,
roles::SECTION,
Attr(xmlroles, region))
MARKUPMAP(summary,
New_HTMLSummary,
roles::SUMMARY)
MARKUPMAP(time,
New_HyperText,
0,
Attr(xmlroles, time),
AttrFromDOM(datetime, datetime))
MARKUPMAP(td,
New_HTMLTableHeaderCellIfScope,
0)
MARKUPMAP(th,
New_HTMLTableHeaderCell,
0)
MARKUPMAP(ul,
New_HTMLList,
roles::LIST)

View file

@ -0,0 +1,947 @@
/* -*- 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 "NotificationController.h"
#include "DocAccessible-inl.h"
#include "DocAccessibleChild.h"
#include "TextLeafAccessible.h"
#include "TextUpdater.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/Element.h"
#include "mozilla/Telemetry.h"
using namespace mozilla;
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector
////////////////////////////////////////////////////////////////////////////////
NotificationController::NotificationController(DocAccessible* aDocument,
nsIPresShell* aPresShell) :
EventQueue(aDocument), mObservingState(eNotObservingRefresh),
mPresShell(aPresShell), mEventGeneration(0)
{
#ifdef DEBUG
mMoveGuardOnStack = false;
#endif
// Schedule initial accessible tree construction.
ScheduleProcessing();
}
NotificationController::~NotificationController()
{
NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
if (mDocument)
Shutdown();
}
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector: AddRef/Release and cycle collection
NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationController)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationController)
if (tmp->mDocument)
tmp->Shutdown();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationController)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHangingChildDocuments)
for (auto it = tmp->mContentInsertions.ConstIter(); !it.Done(); it.Next()) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mContentInsertions key");
cb.NoteXPCOMChild(it.Key());
nsTArray<nsCOMPtr<nsIContent>>* list = it.UserData();
for (uint32_t i = 0; i < list->Length(); i++) {
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
"mContentInsertions value item");
cb.NoteXPCOMChild(list->ElementAt(i));
}
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvents)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRelocations)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(NotificationController, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(NotificationController, Release)
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector: public
void
NotificationController::Shutdown()
{
if (mObservingState != eNotObservingRefresh &&
mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
mObservingState = eNotObservingRefresh;
}
// Shutdown handling child documents.
int32_t childDocCount = mHangingChildDocuments.Length();
for (int32_t idx = childDocCount - 1; idx >= 0; idx--) {
if (!mHangingChildDocuments[idx]->IsDefunct())
mHangingChildDocuments[idx]->Shutdown();
}
mHangingChildDocuments.Clear();
mDocument = nullptr;
mPresShell = nullptr;
mTextHash.Clear();
mContentInsertions.Clear();
mNotifications.Clear();
mEvents.Clear();
mRelocations.Clear();
mEventTree.Clear();
}
EventTree*
NotificationController::QueueMutation(Accessible* aContainer)
{
EventTree* tree = mEventTree.FindOrInsert(aContainer);
if (tree) {
ScheduleProcessing();
}
return tree;
}
bool
NotificationController::QueueMutationEvent(AccTreeMutationEvent* aEvent)
{
// We have to allow there to be a hide and then a show event for a target
// because of targets getting moved. However we need to coalesce a show and
// then a hide for a target which means we need to check for that here.
if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
aEvent->GetAccessible()->ShowEventTarget()) {
AccTreeMutationEvent* showEvent = mMutationMap.GetEvent(aEvent->GetAccessible(), EventMap::ShowEvent);
DropMutationEvent(showEvent);
return false;
}
AccMutationEvent* mutEvent = downcast_accEvent(aEvent);
mEventGeneration++;
mutEvent->SetEventGeneration(mEventGeneration);
if (!mFirstMutationEvent) {
mFirstMutationEvent = aEvent;
ScheduleProcessing();
}
if (mLastMutationEvent) {
NS_ASSERTION(!mLastMutationEvent->NextEvent(), "why isn't the last event the end?");
mLastMutationEvent->SetNextEvent(aEvent);
}
aEvent->SetPrevEvent(mLastMutationEvent);
mLastMutationEvent = aEvent;
mMutationMap.PutEvent(aEvent);
// Because we could be hiding the target of a show event we need to get rid
// of any such events. It may be possible to do less than coallesce all
// events, however that is easiest.
if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
CoalesceMutationEvents();
// mLastMutationEvent will point to something other than aEvent if and only
// if aEvent was just coalesced away. In that case a parent accessible
// must already have the required reorder and text change events so we are
// done here.
if (mLastMutationEvent != aEvent) {
return false;
}
}
// We need to fire a reorder event after all of the events targeted at shown or
// hidden children of a container. So either queue a new one, or move an
// existing one to the end of the queue if the container already has a
// reorder event.
Accessible* target = aEvent->GetAccessible();
Accessible* container = aEvent->GetAccessible()->Parent();
RefPtr<AccReorderEvent> reorder;
if (!container->ReorderEventTarget()) {
reorder = new AccReorderEvent(container);
container->SetReorderEventTarget(true);
mMutationMap.PutEvent(reorder);
// Since this is the first child of container that is changing, the name of
// container may be changing.
QueueNameChange(target);
} else {
AccReorderEvent* event = downcast_accEvent(mMutationMap.GetEvent(container, EventMap::ReorderEvent));
reorder = event;
if (mFirstMutationEvent == event) {
mFirstMutationEvent = event->NextEvent();
} else {
event->PrevEvent()->SetNextEvent(event->NextEvent());
}
event->NextEvent()->SetPrevEvent(event->PrevEvent());
event->SetNextEvent(nullptr);
}
reorder->SetEventGeneration(mEventGeneration);
reorder->SetPrevEvent(mLastMutationEvent);
mLastMutationEvent->SetNextEvent(reorder);
mLastMutationEvent = reorder;
// It is not possible to have a text change event for something other than a
// hyper text accessible.
if (!container->IsHyperText()) {
return true;
}
MOZ_ASSERT(mutEvent);
nsString text;
aEvent->GetAccessible()->AppendTextTo(text);
if (text.IsEmpty()) {
return true;
}
int32_t offset = container->AsHyperText()->GetChildOffset(target);
AccTreeMutationEvent* prevEvent = aEvent->PrevEvent();
while (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
prevEvent = prevEvent->PrevEvent();
}
if (prevEvent && prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE &&
mutEvent->IsHide()) {
AccHideEvent* prevHide = downcast_accEvent(prevEvent);
AccTextChangeEvent* prevTextChange = prevHide->mTextChangeEvent;
if (prevTextChange && prevHide->Parent() == mutEvent->Parent()) {
if (prevHide->mNextSibling == target) {
target->AppendTextTo(prevTextChange->mModifiedText);
prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
} else if (prevHide->mPrevSibling == target) {
nsString temp;
target->AppendTextTo(temp);
uint32_t extraLen = temp.Length();
temp += prevTextChange->mModifiedText;;
prevTextChange->mModifiedText = temp;
prevTextChange->mStart -= extraLen;
prevHide->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
}
}
} else if (prevEvent && mutEvent->IsShow() &&
prevEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
AccShowEvent* prevShow = downcast_accEvent(prevEvent);
AccTextChangeEvent* prevTextChange = prevShow->mTextChangeEvent;
if (prevTextChange && prevShow->Parent() == target->Parent()) {
int32_t index = target->IndexInParent();
int32_t prevIndex = prevShow->GetAccessible()->IndexInParent();
if (prevIndex + 1 == index) {
target->AppendTextTo(prevTextChange->mModifiedText);
prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
} else if (index + 1 == prevIndex) {
nsString temp;
target->AppendTextTo(temp);
prevTextChange->mStart -= temp.Length();
temp += prevTextChange->mModifiedText;
prevTextChange->mModifiedText = temp;
prevShow->mTextChangeEvent.swap(mutEvent->mTextChangeEvent);
}
}
}
if (!mutEvent->mTextChangeEvent) {
mutEvent->mTextChangeEvent =
new AccTextChangeEvent(container, offset, text, mutEvent->IsShow(),
aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
}
return true;
}
void
NotificationController::DropMutationEvent(AccTreeMutationEvent* aEvent)
{
// unset the event bits since the event isn't being fired any more.
if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
aEvent->GetAccessible()->SetReorderEventTarget(false);
} else if (aEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
aEvent->GetAccessible()->SetShowEventTarget(false);
} else {
AccHideEvent* hideEvent = downcast_accEvent(aEvent);
MOZ_ASSERT(hideEvent);
if (hideEvent->NeedsShutdown()) {
mDocument->ShutdownChildrenInSubtree(aEvent->GetAccessible());
}
}
// Do the work to splice the event out of the list.
if (mFirstMutationEvent == aEvent) {
mFirstMutationEvent = aEvent->NextEvent();
} else {
aEvent->PrevEvent()->SetNextEvent(aEvent->NextEvent());
}
if (mLastMutationEvent == aEvent) {
mLastMutationEvent = aEvent->PrevEvent();
} else {
aEvent->NextEvent()->SetPrevEvent(aEvent->PrevEvent());
}
aEvent->SetPrevEvent(nullptr);
aEvent->SetNextEvent(nullptr);
mMutationMap.RemoveEvent(aEvent);
}
void
NotificationController::CoalesceMutationEvents()
{
AccTreeMutationEvent* event = mFirstMutationEvent;
while (event) {
AccTreeMutationEvent* nextEvent = event->NextEvent();
uint32_t eventType = event->GetEventType();
if (event->GetEventType() == nsIAccessibleEvent::EVENT_REORDER) {
Accessible* acc = event->GetAccessible();
while (acc) {
if (acc->IsDoc()) {
break;
}
// if a parent of the reorder event's target is being hidden that
// hide event's target must have a parent that is also a reorder event
// target. That means we don't need this reorder event.
if (acc->HideEventTarget()) {
DropMutationEvent(event);
break;
}
Accessible* parent = acc->Parent();
if (parent->ReorderEventTarget()) {
AccReorderEvent* reorder = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ReorderEvent));
// We want to make sure that a reorder event comes after any show or
// hide events targeted at the children of its target. We keep the
// invariant that event generation goes up as you are farther in the
// queue, so we want to use the spot of the event with the higher
// generation number, and keep that generation number.
if (reorder && reorder->EventGeneration() < event->EventGeneration()) {
reorder->SetEventGeneration(event->EventGeneration());
// It may be true that reorder was before event, and we coalesced
// away all the show / hide events between them. In that case
// event is already immediately after reorder in the queue and we
// do not need to rearrange the list of events.
if (event != reorder->NextEvent()) {
// There really should be a show or hide event before the first
// reorder event.
if (reorder->PrevEvent()) {
reorder->PrevEvent()->SetNextEvent(reorder->NextEvent());
} else {
mFirstMutationEvent = reorder->NextEvent();
}
reorder->NextEvent()->SetPrevEvent(reorder->PrevEvent());
event->PrevEvent()->SetNextEvent(reorder);
reorder->SetPrevEvent(event->PrevEvent());
event->SetPrevEvent(reorder);
reorder->SetNextEvent(event);
}
}
DropMutationEvent(event);
break;
}
acc = parent;
}
} else if (eventType == nsIAccessibleEvent::EVENT_SHOW) {
Accessible* parent = event->GetAccessible()->Parent();
while (parent) {
if (parent->IsDoc()) {
break;
}
// if the parent of a show event is being either shown or hidden then
// we don't need to fire a show event for a subtree of that change.
if (parent->ShowEventTarget() || parent->HideEventTarget()) {
DropMutationEvent(event);
break;
}
parent = parent->Parent();
}
} else {
MOZ_ASSERT(eventType == nsIAccessibleEvent::EVENT_HIDE, "mutation event list has an invalid event");
AccHideEvent* hideEvent = downcast_accEvent(event);
Accessible* parent = hideEvent->Parent();
while (parent) {
if (parent->IsDoc()) {
break;
}
if (parent->HideEventTarget()) {
DropMutationEvent(event);
break;
}
if (parent->ShowEventTarget()) {
AccShowEvent* showEvent = downcast_accEvent(mMutationMap.GetEvent(parent, EventMap::ShowEvent));
if (showEvent->EventGeneration() < hideEvent->EventGeneration()) {
DropMutationEvent(hideEvent);
break;
}
}
parent = parent->Parent();
}
}
event = nextEvent;
}
}
void
NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument)
{
// Schedule child document binding to the tree.
mHangingChildDocuments.AppendElement(aDocument);
ScheduleProcessing();
}
void
NotificationController::ScheduleContentInsertion(Accessible* aContainer,
nsIContent* aStartChildNode,
nsIContent* aEndChildNode)
{
nsTArray<nsCOMPtr<nsIContent>>* list =
mContentInsertions.LookupOrAdd(aContainer);
bool needsProcessing = false;
nsIContent* node = aStartChildNode;
while (node != aEndChildNode) {
// Notification triggers for content insertion even if no content was
// actually inserted, check if the given content has a frame to discard
// this case early.
if (node->GetPrimaryFrame()) {
if (list->AppendElement(node))
needsProcessing = true;
}
node = node->GetNextSibling();
}
if (needsProcessing) {
ScheduleProcessing();
}
}
void
NotificationController::ScheduleProcessing()
{
// If notification flush isn't planed yet start notification flush
// asynchronously (after style and layout).
if (mObservingState == eNotObservingRefresh) {
if (mPresShell->AddRefreshObserver(this, Flush_Display))
mObservingState = eRefreshObserving;
}
}
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector: protected
bool
NotificationController::IsUpdatePending()
{
return mPresShell->IsLayoutFlushObserver() ||
mObservingState == eRefreshProcessingForUpdate ||
mContentInsertions.Count() != 0 || mNotifications.Length() != 0 ||
mTextHash.Count() != 0 ||
!mDocument->HasLoadState(DocAccessible::eTreeConstructed);
}
void
NotificationController::ProcessMutationEvents()
{
// there is no reason to fire a hide event for a child of a show event
// target. That can happen if something is inserted into the tree and
// removed before the next refresh driver tick, but it should not be
// observable outside gecko so it should be safe to coalesce away any such
// events. This means that it should be fine to fire all of the hide events
// first, and then deal with any shown subtrees.
for (AccTreeMutationEvent* event = mFirstMutationEvent;
event; event = event->NextEvent()) {
if (event->GetEventType() != nsIAccessibleEvent::EVENT_HIDE) {
continue;
}
nsEventShell::FireEvent(event);
if (!mDocument) {
return;
}
AccMutationEvent* mutEvent = downcast_accEvent(event);
if (mutEvent->mTextChangeEvent) {
nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
if (!mDocument) {
return;
}
}
// Fire menupopup end event before a hide event if a menu goes away.
// XXX: We don't look into children of hidden subtree to find hiding
// menupopup (as we did prior bug 570275) because we don't do that when
// menu is showing (and that's impossible until bug 606924 is fixed).
// Nevertheless we should do this at least because layout coalesces
// the changes before our processing and we may miss some menupopup
// events. Now we just want to be consistent in content insertion/removal
// handling.
if (event->mAccessible->ARIARole() == roles::MENUPOPUP) {
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END,
event->mAccessible);
if (!mDocument) {
return;
}
}
AccHideEvent* hideEvent = downcast_accEvent(event);
if (hideEvent->NeedsShutdown()) {
mDocument->ShutdownChildrenInSubtree(event->mAccessible);
}
}
// Group the show events by the parent of their target.
nsDataHashtable<nsPtrHashKey<Accessible>, nsTArray<AccTreeMutationEvent*>> showEvents;
for (AccTreeMutationEvent* event = mFirstMutationEvent;
event; event = event->NextEvent()) {
if (event->GetEventType() != nsIAccessibleEvent::EVENT_SHOW) {
continue;
}
Accessible* parent = event->GetAccessible()->Parent();
showEvents.GetOrInsert(parent).AppendElement(event);
}
// We need to fire show events for the children of an accessible in the order
// of their indices at this point. So sort each set of events for the same
// container by the index of their target.
for (auto iter = showEvents.Iter(); !iter.Done(); iter.Next()) {
struct AccIdxComparator {
bool LessThan(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const
{
int32_t aIdx = a->GetAccessible()->IndexInParent();
int32_t bIdx = b->GetAccessible()->IndexInParent();
MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
return aIdx < bIdx;
}
bool Equals(const AccTreeMutationEvent* a, const AccTreeMutationEvent* b) const
{
DebugOnly<int32_t> aIdx = a->GetAccessible()->IndexInParent();
DebugOnly<int32_t> bIdx = b->GetAccessible()->IndexInParent();
MOZ_ASSERT(aIdx >= 0 && bIdx >= 0 && aIdx != bIdx);
return false;
}
};
nsTArray<AccTreeMutationEvent*>& events = iter.Data();
events.Sort(AccIdxComparator());
for (AccTreeMutationEvent* event: events) {
nsEventShell::FireEvent(event);
if (!mDocument) {
return;
}
AccMutationEvent* mutEvent = downcast_accEvent(event);
if (mutEvent->mTextChangeEvent) {
nsEventShell::FireEvent(mutEvent->mTextChangeEvent);
if (!mDocument) {
return;
}
}
}
}
// Now we can fire the reorder events after all the show and hide events.
for (AccTreeMutationEvent* event = mFirstMutationEvent;
event; event = event->NextEvent()) {
if (event->GetEventType() != nsIAccessibleEvent::EVENT_REORDER) {
continue;
}
nsEventShell::FireEvent(event);
if (!mDocument) {
return;
}
Accessible* target = event->GetAccessible();
target->Document()->MaybeNotifyOfValueChange(target);
if (!mDocument) {
return;
}
}
}
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector: private
void
NotificationController::WillRefresh(mozilla::TimeStamp aTime)
{
PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
Telemetry::AutoTimer<Telemetry::A11Y_UPDATE_TIME> updateTimer;
// If the document accessible that notification collector was created for is
// now shut down, don't process notifications anymore.
NS_ASSERTION(mDocument,
"The document was shut down while refresh observer is attached!");
if (!mDocument)
return;
if (mObservingState == eRefreshProcessing ||
mObservingState == eRefreshProcessingForUpdate)
return;
// Any generic notifications should be queued if we're processing content
// insertions or generic notifications.
mObservingState = eRefreshProcessingForUpdate;
// Initial accessible tree construction.
if (!mDocument->HasLoadState(DocAccessible::eTreeConstructed)) {
// If document is not bound to parent at this point then the document is not
// ready yet (process notifications later).
if (!mDocument->IsBoundToParent()) {
mObservingState = eRefreshObserving;
return;
}
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eTree)) {
logging::MsgBegin("TREE", "initial tree created");
logging::Address("document", mDocument);
logging::MsgEnd();
}
#endif
mDocument->DoInitialUpdate();
NS_ASSERTION(mContentInsertions.Count() == 0,
"Pending content insertions while initial accessible tree isn't created!");
}
// Initialize scroll support if needed.
if (!(mDocument->mDocFlags & DocAccessible::eScrollInitialized))
mDocument->AddScrollListener();
// Process rendered text change notifications.
for (auto iter = mTextHash.Iter(); !iter.Done(); iter.Next()) {
nsCOMPtrHashKey<nsIContent>* entry = iter.Get();
nsIContent* textNode = entry->GetKey();
Accessible* textAcc = mDocument->GetAccessible(textNode);
// If the text node is not in tree or doesn't have frame then this case should
// have been handled already by content removal notifications.
nsINode* containerNode = textNode->GetParentNode();
if (!containerNode) {
NS_ASSERTION(!textAcc,
"Text node was removed but accessible is kept alive!");
continue;
}
nsIFrame* textFrame = textNode->GetPrimaryFrame();
if (!textFrame) {
NS_ASSERTION(!textAcc,
"Text node isn't rendered but accessible is kept alive!");
continue;
}
nsIContent* containerElm = containerNode->IsElement() ?
containerNode->AsElement() : nullptr;
nsIFrame::RenderedText text = textFrame->GetRenderedText(0,
UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
// Remove text accessible if rendered text is empty.
if (textAcc) {
if (text.mString.IsEmpty()) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eTree | logging::eText)) {
logging::MsgBegin("TREE", "text node lost its content; doc: %p", mDocument);
logging::Node("container", containerElm);
logging::Node("content", textNode);
logging::MsgEnd();
}
#endif
mDocument->ContentRemoved(containerElm, textNode);
continue;
}
// Update text of the accessible and fire text change events.
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eText)) {
logging::MsgBegin("TEXT", "text may be changed; doc: %p", mDocument);
logging::Node("container", containerElm);
logging::Node("content", textNode);
logging::MsgEntry("old text '%s'",
NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
logging::MsgEntry("new text: '%s'",
NS_ConvertUTF16toUTF8(text.mString).get());
logging::MsgEnd();
}
#endif
TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
continue;
}
// Append an accessible if rendered text is not empty.
if (!text.mString.IsEmpty()) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eTree | logging::eText)) {
logging::MsgBegin("TREE", "text node gains new content; doc: %p", mDocument);
logging::Node("container", containerElm);
logging::Node("content", textNode);
logging::MsgEnd();
}
#endif
Accessible* container = mDocument->AccessibleOrTrueContainer(containerNode);
MOZ_ASSERT(container,
"Text node having rendered text hasn't accessible document!");
if (container) {
nsTArray<nsCOMPtr<nsIContent>>* list =
mContentInsertions.LookupOrAdd(container);
list->AppendElement(textNode);
}
}
}
mTextHash.Clear();
// Process content inserted notifications to update the tree.
for (auto iter = mContentInsertions.ConstIter(); !iter.Done(); iter.Next()) {
mDocument->ProcessContentInserted(iter.Key(), iter.UserData());
if (!mDocument) {
return;
}
}
mContentInsertions.Clear();
// Bind hanging child documents.
uint32_t hangingDocCnt = mHangingChildDocuments.Length();
nsTArray<RefPtr<DocAccessible>> newChildDocs;
for (uint32_t idx = 0; idx < hangingDocCnt; idx++) {
DocAccessible* childDoc = mHangingChildDocuments[idx];
if (childDoc->IsDefunct())
continue;
nsIContent* ownerContent = mDocument->DocumentNode()->
FindContentForSubDocument(childDoc->DocumentNode());
if (ownerContent) {
Accessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
if (mDocument->AppendChildDocument(childDoc)) {
newChildDocs.AppendElement(Move(mHangingChildDocuments[idx]));
continue;
}
outerDocAcc->RemoveChild(childDoc);
}
// Failed to bind the child document, destroy it.
childDoc->Shutdown();
}
}
mHangingChildDocuments.Clear();
// If the document is ready and all its subdocuments are completely loaded
// then process the document load.
if (mDocument->HasLoadState(DocAccessible::eReady) &&
!mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
hangingDocCnt == 0) {
uint32_t childDocCnt = mDocument->ChildDocumentCount(), childDocIdx = 0;
for (; childDocIdx < childDocCnt; childDocIdx++) {
DocAccessible* childDoc = mDocument->GetChildDocumentAt(childDocIdx);
if (!childDoc->HasLoadState(DocAccessible::eCompletelyLoaded))
break;
}
if (childDocIdx == childDocCnt) {
mDocument->ProcessLoad();
if (!mDocument)
return;
}
}
// Process only currently queued generic notifications.
nsTArray < RefPtr<Notification> > notifications;
notifications.SwapElements(mNotifications);
uint32_t notificationCount = notifications.Length();
for (uint32_t idx = 0; idx < notificationCount; idx++) {
notifications[idx]->Process();
if (!mDocument)
return;
}
// Process invalidation list of the document after all accessible tree
// modification are done.
mDocument->ProcessInvalidationList();
// We cannot rely on DOM tree to keep aria-owns relations updated. Make
// a validation to remove dead links.
mDocument->ValidateARIAOwned();
// Process relocation list.
for (uint32_t idx = 0; idx < mRelocations.Length(); idx++) {
if (mRelocations[idx]->IsInDocument()) {
mDocument->DoARIAOwnsRelocation(mRelocations[idx]);
}
}
mRelocations.Clear();
// If a generic notification occurs after this point then we may be allowed to
// process it synchronously. However we do not want to reenter if fireing
// events causes script to run.
mObservingState = eRefreshProcessing;
CoalesceMutationEvents();
ProcessMutationEvents();
mEventGeneration = 0;
// Now that we are done with them get rid of the events we fired.
RefPtr<AccTreeMutationEvent> mutEvent = Move(mFirstMutationEvent);
mLastMutationEvent = nullptr;
mFirstMutationEvent = nullptr;
while (mutEvent) {
RefPtr<AccTreeMutationEvent> nextEvent = mutEvent->NextEvent();
Accessible* target = mutEvent->GetAccessible();
// We need to be careful here, while it may seem that we can simply 0 all
// the pending event bits that is not true. Because accessibles may be
// reparented they may be the target of both a hide event and a show event
// at the same time.
if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_SHOW) {
target->SetShowEventTarget(false);
}
if (mutEvent->GetEventType() == nsIAccessibleEvent::EVENT_HIDE) {
target->SetHideEventTarget(false);
}
// However it is not possible for a reorder event target to also be the
// target of a show or hide, so we can just zero that.
target->SetReorderEventTarget(false);
mutEvent->SetPrevEvent(nullptr);
mutEvent->SetNextEvent(nullptr);
mMutationMap.RemoveEvent(mutEvent);
mutEvent = nextEvent;
}
ProcessEventQueue();
if (IPCAccessibilityActive()) {
size_t newDocCount = newChildDocs.Length();
for (size_t i = 0; i < newDocCount; i++) {
DocAccessible* childDoc = newChildDocs[i];
if (childDoc->IsDefunct()) {
continue;
}
Accessible* parent = childDoc->Parent();
DocAccessibleChild* parentIPCDoc = mDocument->IPCDoc();
uint64_t id = reinterpret_cast<uintptr_t>(parent->UniqueID());
MOZ_ASSERT(id);
DocAccessibleChild* ipcDoc = childDoc->IPCDoc();
if (ipcDoc) {
parentIPCDoc->SendBindChildDoc(ipcDoc, id);
continue;
}
ipcDoc = new DocAccessibleChild(childDoc);
childDoc->SetIPCDoc(ipcDoc);
#if defined(XP_WIN)
MOZ_ASSERT(parentIPCDoc);
parentIPCDoc->ConstructChildDocInParentProcess(ipcDoc, id,
AccessibleWrap::GetChildIDFor(childDoc));
#else
nsCOMPtr<nsITabChild> tabChild =
do_GetInterface(mDocument->DocumentNode()->GetDocShell());
if (tabChild) {
MOZ_ASSERT(parentIPCDoc);
static_cast<TabChild*>(tabChild.get())->
SendPDocAccessibleConstructor(ipcDoc, parentIPCDoc, id, 0, 0);
}
#endif
}
}
mObservingState = eRefreshObserving;
if (!mDocument)
return;
// Stop further processing if there are no new notifications of any kind or
// events and document load is processed.
if (mContentInsertions.Count() == 0 && mNotifications.IsEmpty() &&
mEvents.IsEmpty() && mTextHash.Count() == 0 &&
mHangingChildDocuments.IsEmpty() &&
mDocument->HasLoadState(DocAccessible::eCompletelyLoaded) &&
mPresShell->RemoveRefreshObserver(this, Flush_Display)) {
mObservingState = eNotObservingRefresh;
}
}
void
NotificationController::EventMap::PutEvent(AccTreeMutationEvent* aEvent)
{
EventType type = GetEventType(aEvent);
uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
addr |= type;
mTable.Put(addr, aEvent);
}
AccTreeMutationEvent*
NotificationController::EventMap::GetEvent(Accessible* aTarget, EventType aType)
{
uint64_t addr = reinterpret_cast<uintptr_t>(aTarget);
MOZ_ASSERT((addr & 0x3) == 0, "target is not 4 byte aligned");
addr |= aType;
return mTable.GetWeak(addr);
}
void
NotificationController::EventMap::RemoveEvent(AccTreeMutationEvent* aEvent)
{
EventType type = GetEventType(aEvent);
uint64_t addr = reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
MOZ_ASSERT((addr & 0x3) == 0, "accessible is not 4 byte aligned");
addr |= type;
MOZ_ASSERT(mTable.GetWeak(addr) == aEvent, "mTable has the wrong event");
mTable.Remove(addr);
}
NotificationController::EventMap::EventType
NotificationController::EventMap::GetEventType(AccTreeMutationEvent* aEvent)
{
switch(aEvent->GetEventType())
{
case nsIAccessibleEvent::EVENT_SHOW:
return ShowEvent;
case nsIAccessibleEvent::EVENT_HIDE:
return HideEvent;
case nsIAccessibleEvent::EVENT_REORDER:
return ReorderEvent;
default:
MOZ_ASSERT_UNREACHABLE("event has invalid type");
return ShowEvent;
}
}

View file

@ -0,0 +1,439 @@
/* -*- 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/. */
#ifndef mozilla_a11y_NotificationController_h_
#define mozilla_a11y_NotificationController_h_
#include "EventQueue.h"
#include "EventTree.h"
#include "mozilla/IndexSequence.h"
#include "mozilla/Tuple.h"
#include "nsCycleCollectionParticipant.h"
#include "nsRefreshDriver.h"
#ifdef A11Y_LOG
#include "Logging.h"
#endif
namespace mozilla {
namespace a11y {
class DocAccessible;
/**
* Notification interface.
*/
class Notification
{
public:
NS_INLINE_DECL_REFCOUNTING(mozilla::a11y::Notification)
/**
* Process notification.
*/
virtual void Process() = 0;
protected:
Notification() { }
/**
* Protected destructor, to discourage deletion outside of Release():
*/
virtual ~Notification() { }
private:
Notification(const Notification&);
Notification& operator = (const Notification&);
};
/**
* Template class for generic notification.
*
* @note Instance is kept as a weak ref, the caller must guarantee it exists
* longer than the document accessible owning the notification controller
* that this notification is processed by.
*/
template<class Class, class ... Args>
class TNotification : public Notification
{
public:
typedef void (Class::*Callback)(Args* ...);
TNotification(Class* aInstance, Callback aCallback, Args* ... aArgs) :
mInstance(aInstance), mCallback(aCallback), mArgs(aArgs...) { }
virtual ~TNotification() { mInstance = nullptr; }
virtual void Process() override
{ ProcessHelper(typename IndexSequenceFor<Args...>::Type()); }
private:
TNotification(const TNotification&);
TNotification& operator = (const TNotification&);
template <size_t... Indices>
void ProcessHelper(IndexSequence<Indices...>)
{
(mInstance->*mCallback)(Get<Indices>(mArgs)...);
}
Class* mInstance;
Callback mCallback;
Tuple<RefPtr<Args> ...> mArgs;
};
/**
* Used to process notifications from core for the document accessible.
*/
class NotificationController final : public EventQueue,
public nsARefreshObserver
{
public:
NotificationController(DocAccessible* aDocument, nsIPresShell* aPresShell);
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override;
NS_IMETHOD_(MozExternalRefCountType) Release(void) override;
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController)
/**
* Shutdown the notification controller.
*/
void Shutdown();
/**
* Add an accessible event into the queue to process it later.
*/
void QueueEvent(AccEvent* aEvent)
{
if (PushEvent(aEvent)) {
ScheduleProcessing();
}
}
/**
* Creates and adds a name change event into the queue for a container of
* the given accessible, if the accessible is a part of name computation of
* the container.
*/
void QueueNameChange(Accessible* aChangeTarget)
{
if (PushNameChange(aChangeTarget)) {
ScheduleProcessing();
}
}
/**
* Returns existing event tree for the given the accessible or creates one if
* it doesn't exists yet.
*/
EventTree* QueueMutation(Accessible* aContainer);
class MoveGuard final {
public:
explicit MoveGuard(NotificationController* aController) :
mController(aController)
{
#ifdef DEBUG
MOZ_ASSERT(!mController->mMoveGuardOnStack,
"Move guard is on stack already!");
mController->mMoveGuardOnStack = true;
#endif
}
~MoveGuard() {
#ifdef DEBUG
MOZ_ASSERT(mController->mMoveGuardOnStack, "No move guard on stack!");
mController->mMoveGuardOnStack = false;
#endif
mController->mPrecedingEvents.Clear();
}
private:
NotificationController* mController;
};
#ifdef A11Y_LOG
const EventTree& RootEventTree() const { return mEventTree; };
#endif
/**
* Queue a mutation event to emit if not coalesced away. Returns true if the
* event was queued and has not yet been coalesced.
*/
bool QueueMutationEvent(AccTreeMutationEvent* aEvent);
/**
* Coalesce all queued mutation events.
*/
void CoalesceMutationEvents();
/**
* Schedule binding the child document to the tree of this document.
*/
void ScheduleChildDocBinding(DocAccessible* aDocument);
/**
* Schedule the accessible tree update because of rendered text changes.
*/
inline void ScheduleTextUpdate(nsIContent* aTextNode)
{
// Make sure we are not called with a node that is not in the DOM tree or
// not visible.
MOZ_ASSERT(aTextNode->GetParentNode(), "A text node is not in DOM");
MOZ_ASSERT(aTextNode->GetPrimaryFrame(), "A text node doesn't have a frame");
MOZ_ASSERT(aTextNode->GetPrimaryFrame()->StyleVisibility()->IsVisible(),
"A text node is not visible");
mTextHash.PutEntry(aTextNode);
ScheduleProcessing();
}
/**
* Pend accessible tree update for content insertion.
*/
void ScheduleContentInsertion(Accessible* aContainer,
nsIContent* aStartChildNode,
nsIContent* aEndChildNode);
/**
* Pend an accessible subtree relocation.
*/
void ScheduleRelocation(Accessible* aOwner)
{
if (!mRelocations.Contains(aOwner) && mRelocations.AppendElement(aOwner)) {
ScheduleProcessing();
}
}
/**
* Start to observe refresh to make notifications and events processing after
* layout.
*/
void ScheduleProcessing();
/**
* Process the generic notification synchronously if there are no pending
* layout changes and no notifications are pending or being processed right
* now. Otherwise, queue it up to process asynchronously.
*
* @note The caller must guarantee that the given instance still exists when
* the notification is processed.
*/
template<class Class, class Arg>
inline void HandleNotification(Class* aInstance,
typename TNotification<Class, Arg>::Callback aMethod,
Arg* aArg)
{
if (!IsUpdatePending()) {
#ifdef A11Y_LOG
if (mozilla::a11y::logging::IsEnabled(mozilla::a11y::logging::eNotifications))
mozilla::a11y::logging::Text("sync notification processing");
#endif
(aInstance->*aMethod)(aArg);
return;
}
RefPtr<Notification> notification =
new TNotification<Class, Arg>(aInstance, aMethod, aArg);
if (notification && mNotifications.AppendElement(notification))
ScheduleProcessing();
}
/**
* Schedule the generic notification to process asynchronously.
*
* @note The caller must guarantee that the given instance still exists when
* the notification is processed.
*/
template<class Class>
inline void ScheduleNotification(Class* aInstance,
typename TNotification<Class>::Callback aMethod)
{
RefPtr<Notification> notification =
new TNotification<Class>(aInstance, aMethod);
if (notification && mNotifications.AppendElement(notification))
ScheduleProcessing();
}
#ifdef DEBUG
bool IsUpdating() const
{ return mObservingState == eRefreshProcessingForUpdate; }
#endif
protected:
virtual ~NotificationController();
nsCycleCollectingAutoRefCnt mRefCnt;
NS_DECL_OWNINGTHREAD
/**
* Return true if the accessible tree state update is pending.
*/
bool IsUpdatePending();
private:
NotificationController(const NotificationController&);
NotificationController& operator = (const NotificationController&);
// nsARefreshObserver
virtual void WillRefresh(mozilla::TimeStamp aTime) override;
/**
* Set and returns a hide event, paired with a show event, for the move.
*/
void WithdrawPrecedingEvents(nsTArray<RefPtr<AccHideEvent>>* aEvs)
{
if (mPrecedingEvents.Length() > 0) {
aEvs->AppendElements(mozilla::Move(mPrecedingEvents));
}
}
void StorePrecedingEvent(AccHideEvent* aEv)
{
MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
mPrecedingEvents.AppendElement(aEv);
}
void StorePrecedingEvents(nsTArray<RefPtr<AccHideEvent>>&& aEvs)
{
MOZ_ASSERT(mMoveGuardOnStack, "No move guard on stack!");
mPrecedingEvents.InsertElementsAt(0, aEvs);
}
private:
/**
* get rid of a mutation event that is no longer necessary.
*/
void DropMutationEvent(AccTreeMutationEvent* aEvent);
/**
* Fire all necessary mutation events.
*/
void ProcessMutationEvents();
/**
* Indicates whether we're waiting on an event queue processing from our
* notification controller to flush events.
*/
enum eObservingState {
eNotObservingRefresh,
eRefreshObserving,
eRefreshProcessing,
eRefreshProcessingForUpdate
};
eObservingState mObservingState;
/**
* The presshell of the document accessible.
*/
nsIPresShell* mPresShell;
/**
* Child documents that needs to be bound to the tree.
*/
nsTArray<RefPtr<DocAccessible> > mHangingChildDocuments;
/**
* Pending accessible tree update notifications for content insertions.
*/
nsClassHashtable<nsRefPtrHashKey<Accessible>,
nsTArray<nsCOMPtr<nsIContent>>> mContentInsertions;
template<class T>
class nsCOMPtrHashKey : public PLDHashEntryHdr
{
public:
typedef T* KeyType;
typedef const T* KeyTypePointer;
explicit nsCOMPtrHashKey(const T* aKey) : mKey(const_cast<T*>(aKey)) {}
explicit nsCOMPtrHashKey(const nsPtrHashKey<T> &aToCopy) : mKey(aToCopy.mKey) {}
~nsCOMPtrHashKey() { }
KeyType GetKey() const { return mKey; }
bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; }
static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; }
static PLDHashNumber HashKey(KeyTypePointer aKey)
{ return NS_PTR_TO_INT32(aKey) >> 2; }
enum { ALLOW_MEMMOVE = true };
protected:
nsCOMPtr<T> mKey;
};
/**
* Pending accessible tree update notifications for rendered text changes.
*/
nsTHashtable<nsCOMPtrHashKey<nsIContent> > mTextHash;
/**
* Other notifications like DOM events. Don't make this an AutoTArray; we
* use SwapElements() on it.
*/
nsTArray<RefPtr<Notification> > mNotifications;
/**
* Holds all scheduled relocations.
*/
nsTArray<RefPtr<Accessible> > mRelocations;
/**
* Holds all mutation events.
*/
EventTree mEventTree;
/**
* A temporary collection of hide events that should be fired before related
* show event. Used by EventTree.
*/
nsTArray<RefPtr<AccHideEvent>> mPrecedingEvents;
#ifdef DEBUG
bool mMoveGuardOnStack;
#endif
friend class MoveGuard;
friend class EventTree;
/**
* A list of all mutation events we may want to emit. Ordered from the first
* event that should be emitted to the last one to emit.
*/
RefPtr<AccTreeMutationEvent> mFirstMutationEvent;
RefPtr<AccTreeMutationEvent> mLastMutationEvent;
/**
* A class to map an accessible and event type to an event.
*/
class EventMap
{
public:
enum EventType
{
ShowEvent = 0x0,
HideEvent = 0x1,
ReorderEvent = 0x2,
};
void PutEvent(AccTreeMutationEvent* aEvent);
AccTreeMutationEvent* GetEvent(Accessible* aTarget, EventType aType);
void RemoveEvent(AccTreeMutationEvent* aEvent);
void Clear() { mTable.Clear(); }
private:
EventType GetEventType(AccTreeMutationEvent* aEvent);
nsRefPtrHashtable<nsUint64HashKey, AccTreeMutationEvent> mTable;
};
EventMap mMutationMap;
uint32_t mEventGeneration;
};
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11y_NotificationController_h_

View file

@ -0,0 +1,88 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_Platform_h
#define mozilla_a11y_Platform_h
#include <stdint.h>
class nsString;
namespace mozilla {
namespace a11y {
class ProxyAccessible;
enum EPlatformDisabledState {
ePlatformIsForceEnabled = -1,
ePlatformIsEnabled = 0,
ePlatformIsDisabled = 1
};
/**
* Return the platform disabled state.
*/
EPlatformDisabledState PlatformDisabledState();
#ifdef MOZ_ACCESSIBILITY_ATK
/**
* Perform initialization that should be done as soon as possible, in order
* to minimize startup time.
* XXX: this function and the next defined in ApplicationAccessibleWrap.cpp
*/
void PreInit();
#endif
#if defined(MOZ_ACCESSIBILITY_ATK) || defined(XP_MACOSX)
/**
* Is platform accessibility enabled.
* Only used on linux with atk and MacOS for now.
*/
bool ShouldA11yBeEnabled();
#endif
/**
* Called to initialize platform specific accessibility support.
* Note this is called after internal accessibility support is initialized.
*/
void PlatformInit();
/**
* Shutdown platform accessibility.
* Note this is called before internal accessibility support is shutdown.
*/
void PlatformShutdown();
/**
* called when a new ProxyAccessible is created, so the platform may setup a
* wrapper for it, or take other action.
*/
void ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces);
/**
* Called just before a ProxyAccessible is destroyed so its wrapper can be
* disposed of and other action taken.
*/
void ProxyDestroyed(ProxyAccessible*);
/**
* Callied when an event is fired on a proxied accessible.
*/
void ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType);
void ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t aState,
bool aEnabled);
void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset);
void ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr,
int32_t aStart, uint32_t aLen, bool aIsInsert,
bool aFromUser);
void ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible* aParent,
bool aInsert, bool aFromUser);
void ProxySelectionEvent(ProxyAccessible* aTarget, ProxyAccessible* aWidget,
uint32_t aType);
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11y_Platform_h

108
accessible/base/Relation.h Normal file
View file

@ -0,0 +1,108 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_relation_h_
#define mozilla_a11y_relation_h_
#include "AccIterator.h"
#include <memory>
namespace mozilla {
namespace a11y {
/**
* A collection of relation targets of a certain type. Targets are computed
* lazily while enumerating.
*/
class Relation
{
public:
Relation() : mFirstIter(nullptr), mLastIter(nullptr) { }
explicit Relation(AccIterable* aIter) :
mFirstIter(aIter), mLastIter(aIter) { }
explicit Relation(Accessible* aAcc) :
mFirstIter(nullptr), mLastIter(nullptr)
{ AppendTarget(aAcc); }
Relation(DocAccessible* aDocument, nsIContent* aContent) :
mFirstIter(nullptr), mLastIter(nullptr)
{ AppendTarget(aDocument, aContent); }
Relation(Relation&& aOther) :
mFirstIter(Move(aOther.mFirstIter)), mLastIter(aOther.mLastIter)
{
aOther.mLastIter = nullptr;
}
Relation& operator = (Relation&& aRH)
{
mFirstIter = Move(aRH.mFirstIter);
mLastIter = aRH.mLastIter;
aRH.mLastIter = nullptr;
return *this;
}
inline void AppendIter(AccIterable* aIter)
{
if (mLastIter)
mLastIter->mNextIter.reset(aIter);
else
mFirstIter.reset(aIter);
mLastIter = aIter;
}
/**
* Append the given accessible to the set of related accessibles.
*/
inline void AppendTarget(Accessible* aAcc)
{
if (aAcc)
AppendIter(new SingleAccIterator(aAcc));
}
/**
* Append the one accessible for this content node to the set of related
* accessibles.
*/
void AppendTarget(DocAccessible* aDocument, nsIContent* aContent)
{
if (aContent)
AppendTarget(aDocument->GetAccessible(aContent));
}
/**
* compute and return the next related accessible.
*/
inline Accessible* Next()
{
Accessible* target = nullptr;
while (mFirstIter && !(target = mFirstIter->Next()))
mFirstIter = std::move(mFirstIter->mNextIter);
if (!mFirstIter)
mLastIter = nullptr;
return target;
}
private:
Relation& operator = (const Relation&) = delete;
Relation(const Relation&) = delete;
std::unique_ptr<AccIterable> mFirstIter;
AccIterable* mLastIter;
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,163 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_relationtype_h_
#define mozilla_a11y_relationtype_h_
namespace mozilla {
namespace a11y {
enum class RelationType {
/**
* This object is labelled by a target object.
*/
LABELLED_BY = 0x00,
/**
* This object is label for a target object.
*/
LABEL_FOR = 0x01,
/**
* This object is described by the target object.
*/
DESCRIBED_BY = 0x02,
/**
* This object is describes the target object.
*/
DESCRIPTION_FOR = 0x3,
/**
* This object is a child of a target object.
*/
NODE_CHILD_OF = 0x4,
/**
* This object is a parent of a target object. A dual relation to
* NODE_CHILD_OF.
*/
NODE_PARENT_OF = 0x5,
/**
* Some attribute of this object is affected by a target object.
*/
CONTROLLED_BY = 0x06,
/**
* This object is interactive and controls some attribute of a target object.
*/
CONTROLLER_FOR = 0x07,
/**
* Content flows from this object to a target object, i.e. has content that
* flows logically to another object in a sequential way, e.g. text flow.
*/
FLOWS_TO = 0x08,
/**
* Content flows to this object from a target object, i.e. has content that
* flows logically from another object in a sequential way, e.g. text flow.
*/
FLOWS_FROM = 0x09,
/**
* This object is a member of a group of one or more objects. When there is
* more than one object in the group each member may have one and the same
* target, e.g. a grouping object. It is also possible that each member has
* multiple additional targets, e.g. one for every other member in the group.
*/
MEMBER_OF = 0x0a,
/**
* This object is a sub window of a target object.
*/
SUBWINDOW_OF = 0x0b,
/**
* This object embeds a target object. This relation can be used on the
* OBJID_CLIENT accessible for a top level window to show where the content
* areas are.
*/
EMBEDS = 0x0c,
/**
* This object is embedded by a target object.
*/
EMBEDDED_BY = 0x0d,
/**
* This object is a transient component related to the target object. When
* this object is activated the target object doesn't lose focus.
*/
POPUP_FOR = 0x0e,
/**
* This object is a parent window of the target object.
*/
PARENT_WINDOW_OF = 0x0f,
/**
* Part of a form/dialog with a related default button. It is used for
* MSAA/XPCOM, it isn't for IA2 or ATK.
*/
DEFAULT_BUTTON = 0x10,
/**
* The target object is the containing document object.
*/
CONTAINING_DOCUMENT = 0x11,
/**
* The target object is the topmost containing document object in the tab pane.
*/
CONTAINING_TAB_PANE = 0x12,
/**
* The target object is the containing window object.
*/
CONTAINING_WINDOW = 0x13,
/**
* The target object is the containing application object.
*/
CONTAINING_APPLICATION = 0x14,
/**
* The target object provides the detailed, extended description for this
* object. It provides more detailed information than would normally be
* provided using the DESCRIBED_BY relation. A common use for this relation is
* in digital publishing where an extended description needs to be conveyed in
* a book that requires structural markup or the embedding of other technology
* to provide illustrative content.
*/
DETAILS = 0x15,
/**
* This object provides the detailed, extended description for the target
* object. See DETAILS relation.
*/
DETAILS_FOR = 0x16,
/**
* The target object is the error message for this object.
*/
ERRORMSG = 0x17,
/**
* This object is the error message for the target object.
*/
ERRORMSG_FOR = 0x18,
LAST = ERRORMSG_FOR
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,154 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
/**
* Usage: declare the macro RELATIONTYPE()with the following arguments:
* RELATIONTYPE(geckoType, geckoTypeName, atkType, msaaType, ia2Type)
*/
RELATIONTYPE(LABELLED_BY,
"labelled by",
ATK_RELATION_LABELLED_BY,
NAVRELATION_LABELLED_BY,
IA2_RELATION_LABELLED_BY)
RELATIONTYPE(LABEL_FOR,
"label for",
ATK_RELATION_LABEL_FOR,
NAVRELATION_LABEL_FOR,
IA2_RELATION_LABEL_FOR)
RELATIONTYPE(DESCRIBED_BY,
"described by",
ATK_RELATION_DESCRIBED_BY,
NAVRELATION_DESCRIBED_BY,
IA2_RELATION_DESCRIBED_BY)
RELATIONTYPE(DESCRIPTION_FOR,
"description for",
ATK_RELATION_DESCRIPTION_FOR,
NAVRELATION_DESCRIPTION_FOR,
IA2_RELATION_DESCRIPTION_FOR)
RELATIONTYPE(NODE_CHILD_OF,
"node child of",
ATK_RELATION_NODE_CHILD_OF,
NAVRELATION_NODE_CHILD_OF,
IA2_RELATION_NODE_CHILD_OF)
RELATIONTYPE(NODE_PARENT_OF,
"node parent of",
ATK_RELATION_NODE_PARENT_OF,
NAVRELATION_NODE_PARENT_OF,
IA2_RELATION_NODE_PARENT_OF)
RELATIONTYPE(CONTROLLED_BY,
"controlled by",
ATK_RELATION_CONTROLLED_BY,
NAVRELATION_CONTROLLED_BY,
IA2_RELATION_CONTROLLED_BY)
RELATIONTYPE(CONTROLLER_FOR,
"controller for",
ATK_RELATION_CONTROLLER_FOR,
NAVRELATION_CONTROLLER_FOR,
IA2_RELATION_CONTROLLER_FOR)
RELATIONTYPE(FLOWS_TO,
"flows to",
ATK_RELATION_FLOWS_TO,
NAVRELATION_FLOWS_TO,
IA2_RELATION_FLOWS_TO)
RELATIONTYPE(FLOWS_FROM,
"flows from",
ATK_RELATION_FLOWS_FROM,
NAVRELATION_FLOWS_FROM,
IA2_RELATION_FLOWS_FROM)
RELATIONTYPE(MEMBER_OF,
"member of",
ATK_RELATION_MEMBER_OF,
NAVRELATION_MEMBER_OF,
IA2_RELATION_MEMBER_OF)
RELATIONTYPE(SUBWINDOW_OF,
"subwindow of",
ATK_RELATION_SUBWINDOW_OF,
NAVRELATION_SUBWINDOW_OF,
IA2_RELATION_SUBWINDOW_OF)
RELATIONTYPE(EMBEDS,
"embeds",
ATK_RELATION_EMBEDS,
NAVRELATION_EMBEDS,
IA2_RELATION_EMBEDS)
RELATIONTYPE(EMBEDDED_BY,
"embedded by",
ATK_RELATION_EMBEDDED_BY,
NAVRELATION_EMBEDDED_BY,
IA2_RELATION_EMBEDDED_BY)
RELATIONTYPE(POPUP_FOR,
"popup for",
ATK_RELATION_POPUP_FOR,
NAVRELATION_POPUP_FOR,
IA2_RELATION_POPUP_FOR)
RELATIONTYPE(PARENT_WINDOW_OF,
"parent window of",
ATK_RELATION_PARENT_WINDOW_OF,
NAVRELATION_PARENT_WINDOW_OF,
IA2_RELATION_PARENT_WINDOW_OF)
RELATIONTYPE(DEFAULT_BUTTON,
"default button",
ATK_RELATION_NULL,
NAVRELATION_DEFAULT_BUTTON,
IA2_RELATION_NULL)
RELATIONTYPE(CONTAINING_DOCUMENT,
"containing document",
ATK_RELATION_NULL,
NAVRELATION_CONTAINING_DOCUMENT,
IA2_RELATION_CONTAINING_DOCUMENT)
RELATIONTYPE(CONTAINING_TAB_PANE,
"containing tab pane",
ATK_RELATION_NULL,
NAVRELATION_CONTAINING_TAB_PANE,
IA2_RELATION_CONTAINING_TAB_PANE)
RELATIONTYPE(CONTAINING_APPLICATION,
"containing application",
ATK_RELATION_NULL,
NAVRELATION_CONTAINING_APPLICATION,
IA2_RELATION_CONTAINING_APPLICATION)
RELATIONTYPE(DETAILS,
"details",
ATK_RELATION_NULL,
NAVRELATION_DETAILS,
IA2_RELATION_DETAILS)
RELATIONTYPE(DETAILS_FOR,
"details for",
ATK_RELATION_NULL,
NAVRELATION_DETAILS_FOR,
IA2_RELATION_DETAILS_FOR)
RELATIONTYPE(ERRORMSG,
"error",
ATK_RELATION_NULL,
NAVRELATION_ERROR,
IA2_RELATION_ERROR)
RELATIONTYPE(ERRORMSG_FOR,
"error for",
ATK_RELATION_NULL,
NAVRELATION_ERROR_FOR,
IA2_RELATION_ERROR_FOR)

995
accessible/base/Role.h Normal file
View file

@ -0,0 +1,995 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef _role_h_
#define _role_h_
/**
* @note Make sure to update the localized role names when changing the list.
* @note When adding a new role, be sure to also add it to base/RoleMap.h and
* update nsIAccessibleRole.
*/
namespace mozilla {
namespace a11y {
namespace roles {
enum Role {
/**
* Used when accessible hans't strong defined role.
*/
NOTHING = 0,
/**
* Represents a title or caption bar for a window. It is used by MSAA only,
* supported automatically by MS Windows.
*/
TITLEBAR = 1,
/**
* Represents the menu bar (positioned beneath the title bar of a window)
* from which menus are selected by the user. The role is used by
* xul:menubar or role="menubar".
*/
MENUBAR = 2,
/**
* Represents a vertical or horizontal scroll bar, which is part of the client
* area or used in a control.
*/
SCROLLBAR = 3,
/**
* Represents a special mouse pointer, which allows a user to manipulate user
* interface elements such as windows. For example, a user clicks and drags
* a sizing grip in the lower-right corner of a window to resize it.
*/
GRIP = 4,
/**
* Represents a system sound, which is associated with various system events.
*/
SOUND = 5,
/**
* Represents the system mouse pointer.
*/
CURSOR = 6,
/**
* Represents the system caret. The role is supported for caret.
*/
CARET = 7,
/**
* Represents an alert or a condition that a user should be notified about.
* Assistive Technologies typically respond to the role by reading the entire
* onscreen contents of containers advertising this role. Should be used for
* warning dialogs, etc. The role is used by xul:browsermessage,
* role="alert".
*/
ALERT = 8,
/**
* Represents the window frame, which contains child objects such as
* a title bar, client, and other objects contained in a window. The role
* is supported automatically by MS Windows.
*/
WINDOW = 9,
/**
* A sub-document (<frame> or <iframe>)
*/
INTERNAL_FRAME = 10,
/**
* Represents a menu, which presents a list of options from which the user can
* make a selection to perform an action. It is used for role="menu".
*/
MENUPOPUP = 11,
/**
* Represents a menu item, which is an entry in a menu that a user can choose
* to carry out a command, select an option. It is used for xul:menuitem,
* role="menuitem".
*/
MENUITEM = 12,
/**
* Represents a ToolTip that provides helpful hints.
*/
TOOLTIP = 13,
/**
* Represents a main window for an application. It is used for
* role="application". Also refer to APP_ROOT
*/
APPLICATION = 14,
/**
* Represents a document window. A document window is always contained within
* an application window. It is used for role="document".
*/
DOCUMENT = 15,
/**
* Represents a pane within a frame or document window. Users can navigate
* between panes and within the contents of the current pane, but cannot
* navigate between items in different panes. Thus, panes represent a level
* of grouping lower than frame windows or documents, but above individual
* controls. It is used for the first child of a <frame> or <iframe>.
*/
PANE = 16,
/**
* Represents a graphical image used to represent data.
*/
CHART = 17,
/**
* Represents a dialog box or message box. It is used for xul:dialog,
* role="dialog".
*/
DIALOG = 18,
/**
* Represents a window border.
*/
BORDER = 19,
/**
* Logically groups other objects. There is not always a parent-child
* relationship between the grouping object and the objects it contains. It
* is used for html:textfield, xul:groupbox, role="group".
*/
GROUPING = 20,
/**
* Used to visually divide a space into two regions, such as a separator menu
* item or a bar that divides split panes within a window. It is used for
* xul:separator, html:hr, role="separator".
*/
SEPARATOR = 21,
/**
* Represents a toolbar, which is a grouping of controls (push buttons or
* toggle buttons) that provides easy access to frequently used features. It
* is used for xul:toolbar, role="toolbar".
*/
TOOLBAR = 22,
/**
* Represents a status bar, which is an area at the bottom of a window that
* displays information about the current operation, state of the application,
* or selected object. The status bar has multiple fields, which display
* different kinds of information. It is used for xul:statusbar.
*/
STATUSBAR = 23,
/**
* Represents a table that contains rows and columns of cells, and optionally,
* row headers and column headers. It is used for html:table,
* role="grid". Also refer to the following role: COLUMNHEADER,
* ROWHEADER, COLUMN, ROW, CELL.
*/
TABLE = 24,
/**
* Represents a column header, providing a visual label for a column in
* a table. It is used for XUL tree column headers, html:th,
* role="colheader". Also refer to TABLE.
*/
COLUMNHEADER = 25,
/**
* Represents a row header, which provides a visual label for a table row.
* It is used for role="rowheader". Also, see TABLE.
*/
ROWHEADER = 26,
/**
* Represents a column of cells within a table. Also, see TABLE.
*/
COLUMN = 27,
/**
* Represents a row of cells within a table. Also, see TABLE.
*/
ROW = 28,
/**
* Represents a cell within a table. It is used for html:td,
* xul:tree cell and xul:listcell. Also, see TABLE.
*/
CELL = 29,
/**
* Represents a link to something else. This object might look like text or
* a graphic, but it acts like a button. It is used for
* xul:label@class="text-link", html:a, html:area.
*/
LINK = 30,
/**
* Displays a Help topic in the form of a ToolTip or Help balloon.
*/
HELPBALLOON = 31,
/**
* Represents a cartoon-like graphic object, such as Microsoft Office
* Assistant, which is displayed to provide help to users of an application.
*/
CHARACTER = 32,
/**
* Represents a list box, allowing the user to select one or more items. It
* is used for xul:listbox, html:select@size, role="list". See also
* LIST_ITEM.
*/
LIST = 33,
/**
* Represents an item in a list. See also LIST.
*/
LISTITEM = 34,
/**
* Represents an outline or tree structure, such as a tree view control,
* that displays a hierarchical list and allows the user to expand and
* collapse branches. Is is used for role="tree".
*/
OUTLINE = 35,
/**
* Represents an item in an outline or tree structure. It is used for
* role="treeitem".
*/
OUTLINEITEM = 36,
/**
* Represents a page tab, it is a child of a page tab list. It is used for
* xul:tab, role="treeitem". Also refer to PAGETABLIST.
*/
PAGETAB = 37,
/**
* Represents a property sheet. It is used for xul:tabpanel,
* role="tabpanel".
*/
PROPERTYPAGE = 38,
/**
* Represents an indicator, such as a pointer graphic, that points to the
* current item.
*/
INDICATOR = 39,
/**
* Represents a picture. Is is used for xul:image, html:img.
*/
GRAPHIC = 40,
/**
* Represents read-only text, such as labels for other controls or
* instructions in a dialog box. Static text cannot be modified or selected.
* Is is used for xul:label, xul:description, html:label, role="label".
*/
STATICTEXT = 41,
/**
* Represents selectable text that allows edits or is designated read-only.
*/
TEXT_LEAF = 42,
/**
* Represents a push button control. It is used for xul:button, html:button,
* role="button".
*/
PUSHBUTTON = 43,
/**
* Represents a check box control. It is used for xul:checkbox,
* html:input@type="checkbox", role="checkbox".
*/
CHECKBUTTON = 44,
/**
* Represents an option button, also called a radio button. It is one of a
* group of mutually exclusive options. All objects sharing a single parent
* that have this attribute are assumed to be part of single mutually
* exclusive group. It is used for xul:radio, html:input@type="radio",
* role="radio".
*/
RADIOBUTTON = 45,
/**
* Represents a combo box; an edit control with an associated list box that
* provides a set of predefined choices. It is used for html:select,
* xul:menulist, role="combobox".
*/
COMBOBOX = 46,
/**
* Represents the calendar control.
*/
DROPLIST = 47,
/**
* Represents a progress bar, dynamically showing the user the percent
* complete of an operation in progress. It is used for xul:progressmeter,
* role="progressbar".
*/
PROGRESSBAR = 48,
/**
* Represents a dial or knob whose purpose is to allow a user to set a value.
*/
DIAL = 49,
/**
* Represents a hot-key field that allows the user to enter a combination or
* sequence of keystrokes.
*/
HOTKEYFIELD = 50,
/**
* Represents a slider, which allows the user to adjust a setting in given
* increments between minimum and maximum values. It is used by xul:scale,
* role="slider".
*/
SLIDER = 51,
/**
* Represents a spin box, which is a control that allows the user to increment
* or decrement the value displayed in a separate "buddy" control associated
* with the spin box. It is used for xul:spinbuttons.
*/
SPINBUTTON = 52,
/**
* Represents a graphical image used to diagram data. It is used for svg:svg.
*/
DIAGRAM = 53,
/**
* Represents an animation control, which contains content that changes over
* time, such as a control that displays a series of bitmap frames.
*/
ANIMATION = 54,
/**
* Represents a mathematical equation. It is used by MATHML, where there is a
* rich DOM subtree for an equation. Use FLAT_EQUATION for <img role="math" alt="[TeX]"/>
*/
EQUATION = 55,
/**
* Represents a button that drops down a list of items.
*/
BUTTONDROPDOWN = 56,
/**
* Represents a button that drops down a menu.
*/
BUTTONMENU = 57,
/**
* Represents a button that drops down a grid. It is used for xul:colorpicker.
*/
BUTTONDROPDOWNGRID = 58,
/**
* Represents blank space between other objects.
*/
WHITESPACE = 59,
/**
* Represents a container of page tab controls. Is it used for xul:tabs,
* DHTML: role="tabs". Also refer to PAGETAB.
*/
PAGETABLIST = 60,
/**
* Represents a control that displays time.
*/
CLOCK = 61,
/**
* Represents a button on a toolbar that has a drop-down list icon directly
* adjacent to the button.
*/
SPLITBUTTON = 62,
/**
* Represents an edit control designed for an Internet Protocol (IP) address.
* The edit control is divided into sections for the different parts of the
* IP address.
*/
IPADDRESS = 63,
/**
* Represents a label control that has an accelerator.
*/
ACCEL_LABEL = 64,
/**
* Represents an arrow in one of the four cardinal directions.
*/
ARROW = 65,
/**
* Represents a control that can be drawn into and is used to trap events.
* It is used for html:canvas.
*/
CANVAS = 66,
/**
* Represents a menu item with a check box.
*/
CHECK_MENU_ITEM = 67,
/**
* Represents a specialized dialog that lets the user choose a color.
*/
COLOR_CHOOSER = 68,
/**
* Represents control whose purpose is to allow a user to edit a date.
*/
DATE_EDITOR = 69,
/**
* An iconified internal frame in an DESKTOP_PANE. Also refer to
* INTERNAL_FRAME.
*/
DESKTOP_ICON = 70,
/**
* A desktop pane. A pane that supports internal frames and iconified
* versions of those internal frames.
*/
DESKTOP_FRAME = 71,
/**
* A directory pane. A pane that allows the user to navigate through
* and select the contents of a directory. May be used by a file chooser.
* Also refer to FILE_CHOOSER.
*/
DIRECTORY_PANE = 72,
/**
* A file chooser. A specialized dialog that displays the files in the
* directory and lets the user select a file, browse a different directory,
* or specify a filename. May use the directory pane to show the contents of
* a directory. Also refer to DIRECTORY_PANE.
*/
FILE_CHOOSER = 73,
/**
* A font chooser. A font chooser is a component that lets the user pick
* various attributes for fonts.
*/
FONT_CHOOSER = 74,
/**
* Frame role. A top level window with a title bar, border, menu bar, etc.
* It is often used as the primary window for an application.
*/
CHROME_WINDOW = 75,
/**
* A glass pane. A pane that is guaranteed to be painted on top of all
* panes beneath it. Also refer to ROOT_PANE.
*/
GLASS_PANE = 76,
/**
* A document container for HTML, whose children represent the document
* content.
*/
HTML_CONTAINER = 77,
/**
* A small fixed size picture, typically used to decorate components.
*/
ICON = 78,
/**
* Presents an icon or short string in an interface.
*/
LABEL = 79,
/**
* A layered pane. A specialized pane that allows its children to be drawn
* in layers, providing a form of stacking order. This is usually the pane
* that holds the menu bar as well as the pane that contains most of the
* visual components in a window. Also refer to GLASS_PANE and
* ROOT_PANE.
*/
LAYERED_PANE = 80,
/**
* A specialized pane whose primary use is inside a dialog.
*/
OPTION_PANE = 81,
/**
* A text object uses for passwords, or other places where the text content
* is not shown visibly to the user.
*/
PASSWORD_TEXT = 82,
/**
* A temporary window that is usually used to offer the user a list of
* choices, and then hides when the user selects one of those choices.
*/
POPUP_MENU = 83,
/**
* A radio button that is a menu item.
*/
RADIO_MENU_ITEM = 84,
/**
* A root pane. A specialized pane that has a glass pane and a layered pane
* as its children. Also refer to GLASS_PANE and LAYERED_PANE.
*/
ROOT_PANE = 85,
/**
* A scroll pane. An object that allows a user to incrementally view a large
* amount of information. Its children can include scroll bars and a
* viewport. Also refer to VIEW_PORT.
*/
SCROLL_PANE = 86,
/**
* A split pane. A specialized panel that presents two other panels at the
* same time. Between the two panels is a divider the user can manipulate to
* make one panel larger and the other panel smaller.
*/
SPLIT_PANE = 87,
/**
* The header for a column of a table.
* XXX: it looks this role is dupe of COLUMNHEADER.
*/
TABLE_COLUMN_HEADER = 88,
/**
* The header for a row of a table.
* XXX: it looks this role is dupe of ROWHEADER
*/
TABLE_ROW_HEADER = 89,
/**
* A menu item used to tear off and reattach its menu.
*/
TEAR_OFF_MENU_ITEM = 90,
/**
* Represents an accessible terminal.
*/
TERMINAL = 91,
/**
* Collection of objects that constitute a logical text entity.
*/
TEXT_CONTAINER = 92,
/**
* A toggle button. A specialized push button that can be checked or
* unchecked, but does not provide a separate indicator for the current state.
*/
TOGGLE_BUTTON = 93,
/**
* Represent a control that is capable of expanding and collapsing rows as
* well as showing multiple columns of data.
*/
TREE_TABLE = 94,
/**
* A viewport. An object usually used in a scroll pane. It represents the
* portion of the entire data that the user can see. As the user manipulates
* the scroll bars, the contents of the viewport can change. Also refer to
* SCROLL_PANE.
*/
VIEWPORT = 95,
/**
* Header of a document page. Also refer to FOOTER.
*/
HEADER = 96,
/**
* Footer of a document page. Also refer to HEADER.
*/
FOOTER = 97,
/**
* A paragraph of text.
*/
PARAGRAPH = 98,
/**
* A ruler such as those used in word processors.
*/
RULER = 99,
/**
* A text entry having dialog or list containing items for insertion into
* an entry widget, for instance a list of words for completion of a
* text entry. It is used for xul:textbox@autocomplete
*/
AUTOCOMPLETE = 100,
/**
* An editable text object in a toolbar.
*/
EDITBAR = 101,
/**
* An control whose textual content may be entered or modified by the user.
*/
ENTRY = 102,
/**
* A caption describing another object.
*/
CAPTION = 103,
/**
* A visual frame or container which contains a view of document content.
* Document frames may occur within another Document instance, in which case
* the second document may be said to be embedded in the containing instance.
* HTML frames are often DOCUMENT_FRAME. Either this object, or a
* singleton descendant, should implement the Document interface.
*/
DOCUMENT_FRAME = 104,
/**
* Heading.
*/
HEADING = 105,
/**
* An object representing a page of document content. It is used in documents
* which are accessed by the user on a page by page basis.
*/
PAGE = 106,
/**
* A container of document content. An example of the use of this role is to
* represent an html:div.
*/
SECTION = 107,
/**
* An object which is redundant with another object in the accessible
* hierarchy. ATs typically ignore objects with this role.
*/
REDUNDANT_OBJECT = 108,
/**
* A container of form controls. An example of the use of this role is to
* represent an html:form.
*/
FORM = 109,
/**
* An object which is used to allow input of characters not found on a
* keyboard, such as the input of Chinese characters on a Western keyboard.
*/
IME = 110,
/**
* XXX: document this.
*/
APP_ROOT = 111,
/**
* Represents a menu item, which is an entry in a menu that a user can choose
* to display another menu.
*/
PARENT_MENUITEM = 112,
/**
* A calendar that allows the user to select a date.
*/
CALENDAR = 113,
/**
* A list of items that is shown by combobox.
*/
COMBOBOX_LIST = 114,
/**
* A item of list that is shown by combobox.
*/
COMBOBOX_OPTION = 115,
/**
* An image map -- has child links representing the areas
*/
IMAGE_MAP = 116,
/**
* An option in a listbox
*/
OPTION = 117,
/**
* A rich option in a listbox, it can have other widgets as children
*/
RICH_OPTION = 118,
/**
* A list of options
*/
LISTBOX = 119,
/**
* Represents a mathematical equation in the accessible name
*/
FLAT_EQUATION = 120,
/**
* Represents a cell within a grid. It is used for role="gridcell". Unlike
* CELL, it allows the calculation of the accessible name from subtree.
* Also, see TABLE.
*/
GRID_CELL = 121,
/**
* Represents an embedded object. It is used for html:object or html:embed.
*/
EMBEDDED_OBJECT = 122,
/**
* A note. Originally intended to be hidden until activated, but now also used
* for things like html 'aside'.
*/
NOTE = 123,
/**
* A figure. Used for things like HTML5 figure element.
*/
FIGURE = 124,
/**
* Represents a rich item with a check box.
*/
CHECK_RICH_OPTION = 125,
/**
* Represent a definition list (dl in HTML).
*/
DEFINITION_LIST = 126,
/**
* Represent a term in a definition list (dt in HTML).
*/
TERM = 127,
/**
* Represent a definition in a definition list (dd in HTML)
*/
DEFINITION = 128,
/**
* Represent a keyboard or keypad key (ARIA role "key").
*/
KEY = 129,
/**
* Represent a switch control widget (ARIA role "switch").
*/
SWITCH = 130,
/**
* A block of MathML code (math).
*/
MATHML_MATH = 131,
/**
* A MathML identifier (mi in MathML).
*/
MATHML_IDENTIFIER = 132,
/**
* A MathML number (mn in MathML).
*/
MATHML_NUMBER = 133,
/**
* A MathML operator (mo in MathML).
*/
MATHML_OPERATOR = 134,
/**
* A MathML text (mtext in MathML).
*/
MATHML_TEXT = 135,
/**
* A MathML string literal (ms in MathML).
*/
MATHML_STRING_LITERAL = 136,
/**
* A MathML glyph (mglyph in MathML).
*/
MATHML_GLYPH = 137,
/**
* A MathML row (mrow in MathML).
*/
MATHML_ROW = 138,
/**
* A MathML fraction (mfrac in MathML).
*/
MATHML_FRACTION = 139,
/**
* A MathML square root (msqrt in MathML).
*/
MATHML_SQUARE_ROOT = 140,
/**
* A MathML root (mroot in MathML).
*/
MATHML_ROOT = 141,
/**
* A MathML fenced element (mfenced in MathML).
*/
MATHML_FENCED = 142,
/**
* A MathML enclosed element (menclose in MathML).
*/
MATHML_ENCLOSED = 143,
/**
* A MathML styling element (mstyle in MathML).
*/
MATHML_STYLE = 144,
/**
* A MathML subscript (msub in MathML).
*/
MATHML_SUB = 145,
/**
* A MathML superscript (msup in MathML).
*/
MATHML_SUP = 146,
/**
* A MathML subscript and superscript (msubsup in MathML).
*/
MATHML_SUB_SUP = 147,
/**
* A MathML underscript (munder in MathML).
*/
MATHML_UNDER = 148,
/**
* A MathML overscript (mover in MathML).
*/
MATHML_OVER = 149,
/**
* A MathML underscript and overscript (munderover in MathML).
*/
MATHML_UNDER_OVER = 150,
/**
* A MathML multiple subscript and superscript element (mmultiscripts in
* MathML).
*/
MATHML_MULTISCRIPTS = 151,
/**
* A MathML table (mtable in MathML).
*/
MATHML_TABLE = 152,
/**
* A MathML labelled table row (mlabeledtr in MathML).
*/
MATHML_LABELED_ROW = 153,
/**
* A MathML table row (mtr in MathML).
*/
MATHML_TABLE_ROW = 154,
/**
* A MathML table entry or cell (mtd in MathML).
*/
MATHML_CELL = 155,
/**
* A MathML interactive element (maction in MathML).
*/
MATHML_ACTION = 156,
/**
* A MathML error message (merror in MathML).
*/
MATHML_ERROR = 157,
/**
* A MathML stacked (rows of numbers) element (mstack in MathML).
*/
MATHML_STACK = 158,
/**
* A MathML long division element (mlongdiv in MathML).
*/
MATHML_LONG_DIVISION = 159,
/**
* A MathML stack group (msgroup in MathML).
*/
MATHML_STACK_GROUP = 160,
/**
* A MathML stack row (msrow in MathML).
*/
MATHML_STACK_ROW = 161,
/**
* MathML carries, borrows, or crossouts for a row (mscarries in MathML).
*/
MATHML_STACK_CARRIES = 162,
/**
* A MathML carry, borrow, or crossout for a column (mscarry in MathML).
*/
MATHML_STACK_CARRY = 163,
/**
* A MathML line in a stack (msline in MathML).
*/
MATHML_STACK_LINE = 164,
/**
* A group containing radio buttons
*/
RADIO_GROUP = 165,
/**
* A text container exposing brief amount of information. See related
* TEXT_CONTAINER role.
*/
TEXT = 166,
/**
* The html:details element.
*/
DETAILS = 167,
/**
* The html:summary element.
*/
SUMMARY = 168,
LAST_ROLE = SUMMARY
};
} // namespace role
typedef enum mozilla::a11y::roles::Role role;
} // namespace a11y
} // namespace mozilla
#endif

1370
accessible/base/RoleMap.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,232 @@
/* -*- Mode: C++; tab-width: 4; 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 "mozilla/a11y/SelectionManager.h"
#include "DocAccessible-inl.h"
#include "HyperTextAccessible.h"
#include "HyperTextAccessible-inl.h"
#include "nsAccessibilityService.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "nsEventShell.h"
#include "nsFrameSelection.h"
#include "nsIAccessibleTypes.h"
#include "nsIDOMDocument.h"
#include "nsIPresShell.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Element.h"
using namespace mozilla;
using namespace mozilla::a11y;
using mozilla::dom::Selection;
struct mozilla::a11y::SelData final
{
SelData(Selection* aSel, int32_t aReason) :
mSel(aSel), mReason(aReason) {}
RefPtr<Selection> mSel;
int16_t mReason;
NS_INLINE_DECL_REFCOUNTING(SelData)
private:
// Private destructor, to discourage deletion outside of Release():
~SelData() {}
};
SelectionManager::SelectionManager() :
mCaretOffset(-1), mAccWithCaret(nullptr)
{
}
void
SelectionManager::ClearControlSelectionListener()
{
// Remove 'this' registered as selection listener for the normal selection.
nsCOMPtr<nsISelection> normalSel = do_QueryReferent(mCurrCtrlNormalSel);
if (normalSel) {
normalSel->AsSelection()->RemoveSelectionListener(this);
mCurrCtrlNormalSel = nullptr;
}
// Remove 'this' registered as selection listener for the spellcheck
// selection.
nsCOMPtr<nsISelection> spellSel = do_QueryReferent(mCurrCtrlSpellSel);
if (spellSel) {
spellSel->AsSelection()->RemoveSelectionListener(this);
mCurrCtrlSpellSel = nullptr;
}
}
void
SelectionManager::SetControlSelectionListener(dom::Element* aFocusedElm)
{
// When focus moves such that the caret is part of a new frame selection
// this removes the old selection listener and attaches a new one for
// the current focus.
ClearControlSelectionListener();
nsIFrame* controlFrame = aFocusedElm->GetPrimaryFrame();
if (!controlFrame)
return;
const nsFrameSelection* frameSel = controlFrame->GetConstFrameSelection();
NS_ASSERTION(frameSel, "No frame selection for focused element!");
if (!frameSel)
return;
// Register 'this' as selection listener for the normal selection.
nsCOMPtr<nsISelection> normalSel = frameSel->GetSelection(SelectionType::eNormal);
normalSel->AsSelection()->AddSelectionListener(this);
mCurrCtrlNormalSel = do_GetWeakReference(normalSel);
// Register 'this' as selection listener for the spell check selection.
nsCOMPtr<nsISelection> spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
spellSel->AsSelection()->AddSelectionListener(this);
mCurrCtrlSpellSel = do_GetWeakReference(spellSel);
}
void
SelectionManager::AddDocSelectionListener(nsIPresShell* aPresShell)
{
const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
// Register 'this' as selection listener for the normal selection.
Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
normalSel->AddSelectionListener(this);
// Register 'this' as selection listener for the spell check selection.
Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
spellSel->AddSelectionListener(this);
}
void
SelectionManager::RemoveDocSelectionListener(nsIPresShell* aPresShell)
{
const nsFrameSelection* frameSel = aPresShell->ConstFrameSelection();
// Remove 'this' registered as selection listener for the normal selection.
Selection* normalSel = frameSel->GetSelection(SelectionType::eNormal);
normalSel->RemoveSelectionListener(this);
// Remove 'this' registered as selection listener for the spellcheck
// selection.
Selection* spellSel = frameSel->GetSelection(SelectionType::eSpellCheck);
spellSel->RemoveSelectionListener(this);
}
void
SelectionManager::ProcessTextSelChangeEvent(AccEvent* aEvent)
{
// Fire selection change event if it's not pure caret-move selection change,
// i.e. the accessible has or had not collapsed selection.
AccTextSelChangeEvent* event = downcast_accEvent(aEvent);
if (!event->IsCaretMoveOnly())
nsEventShell::FireEvent(aEvent);
// Fire caret move event if there's a caret in the selection.
nsINode* caretCntrNode =
nsCoreUtils::GetDOMNodeFromDOMPoint(event->mSel->GetFocusNode(),
event->mSel->FocusOffset());
if (!caretCntrNode)
return;
HyperTextAccessible* caretCntr = nsAccUtils::GetTextContainer(caretCntrNode);
NS_ASSERTION(caretCntr,
"No text container for focus while there's one for common ancestor?!");
if (!caretCntr)
return;
Selection* selection = caretCntr->DOMSelection();
// XXX Sometimes we can't get a selection for caretCntr, in that case assume
// event->mSel is correct.
if (!selection)
selection = event->mSel;
mCaretOffset = caretCntr->DOMPointToOffset(selection->GetFocusNode(),
selection->FocusOffset());
mAccWithCaret = caretCntr;
if (mCaretOffset != -1) {
RefPtr<AccCaretMoveEvent> caretMoveEvent =
new AccCaretMoveEvent(caretCntr, mCaretOffset, aEvent->FromUserInput());
nsEventShell::FireEvent(caretMoveEvent);
}
}
NS_IMETHODIMP
SelectionManager::NotifySelectionChanged(nsIDOMDocument* aDOMDocument,
nsISelection* aSelection,
int16_t aReason)
{
if (NS_WARN_IF(!aDOMDocument) || NS_WARN_IF(!aSelection)) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsIDocument> documentNode(do_QueryInterface(aDOMDocument));
DocAccessible* document = GetAccService()->GetDocAccessible(documentNode);
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eSelection))
logging::SelChange(aSelection, document, aReason);
#endif
if (document) {
// Selection manager has longer lifetime than any document accessible,
// so that we are guaranteed that the notification is processed before
// the selection manager is destroyed.
RefPtr<SelData> selData =
new SelData(aSelection->AsSelection(), aReason);
document->HandleNotification<SelectionManager, SelData>
(this, &SelectionManager::ProcessSelectionChanged, selData);
}
return NS_OK;
}
void
SelectionManager::ProcessSelectionChanged(SelData* aSelData)
{
Selection* selection = aSelData->mSel;
if (!selection->GetPresShell())
return;
const nsRange* range = selection->GetAnchorFocusRange();
nsINode* cntrNode = nullptr;
if (range)
cntrNode = range->GetCommonAncestor();
if (!cntrNode) {
cntrNode = selection->GetFrameSelection()->GetAncestorLimiter();
if (!cntrNode) {
cntrNode = selection->GetPresShell()->GetDocument();
NS_ASSERTION(aSelData->mSel->GetPresShell()->ConstFrameSelection() == selection->GetFrameSelection(),
"Wrong selection container was used!");
}
}
HyperTextAccessible* text = nsAccUtils::GetTextContainer(cntrNode);
if (!text) {
NS_NOTREACHED("We must reach document accessible implementing text interface!");
return;
}
if (selection->GetType() == SelectionType::eNormal) {
RefPtr<AccEvent> event =
new AccTextSelChangeEvent(text, selection, aSelData->mReason);
text->Document()->FireDelayedEvent(event);
} else if (selection->GetType() == SelectionType::eSpellCheck) {
// XXX: fire an event for container accessible of the focus/anchor range
// of the spelcheck selection.
text->Document()->FireDelayedEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED,
text);
}
}

View file

@ -0,0 +1,133 @@
/* -*- Mode: C++; tab-width: 4; 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/. */
#ifndef mozilla_a11y_SelectionManager_h__
#define mozilla_a11y_SelectionManager_h__
#include "nsIFrame.h"
#include "nsISelectionListener.h"
class nsIPresShell;
namespace mozilla {
namespace dom {
class Element;
}
namespace a11y {
class AccEvent;
class HyperTextAccessible;
/**
* This special accessibility class is for the caret and selection management.
* There is only 1 visible caret per top level window. However, there may be
* several visible selections.
*
* The important selections are the one owned by each document, and the one in
* the currently focused control.
*
* On Windows this class is used to move an invisible system caret that
* shadows the Mozilla caret. Windows will also automatically map this to
* the MSAA caret accessible object (via OBJID_CARET) (as opposed to the root
* accessible tree for a window which is retrieved with OBJID_CLIENT).
*
* For ATK and IAccessible2, this class is used to fire caret move and
* selection change events.
*/
struct SelData;
class SelectionManager : public nsISelectionListener
{
public:
// nsISupports
// implemented by derived nsAccessibilityService
// nsISelectionListener
NS_DECL_NSISELECTIONLISTENER
// SelectionManager
void Shutdown() { ClearControlSelectionListener(); }
/**
* Listen to selection events on the focused control.
*
* Note: only one control's selection events are listened to at a time. This
* will remove the previous control's selection listener.
*/
void SetControlSelectionListener(dom::Element* aFocusedElm);
/**
* Stop listening to selection events on the control.
*/
void ClearControlSelectionListener();
/**
* Listen to selection events on the document.
*/
void AddDocSelectionListener(nsIPresShell* aPresShell);
/**
* Stop listening to selection events for a given document
*/
void RemoveDocSelectionListener(nsIPresShell* aShell);
/**
* Process delayed event, results in caret move and text selection change
* events.
*/
void ProcessTextSelChangeEvent(AccEvent* aEvent);
/**
* Gets the current caret offset/hypertext accessible pair. If there is no
* current pair, then returns -1 for the offset and a nullptr for the
* accessible.
*/
inline HyperTextAccessible* AccessibleWithCaret(int32_t* aCaret)
{
if (aCaret)
*aCaret = mCaretOffset;
return mAccWithCaret;
}
/**
* Update caret offset when it doesn't go through a caret move event.
*/
inline void UpdateCaretOffset(HyperTextAccessible* aItem, int32_t aOffset)
{
mAccWithCaret = aItem;
mCaretOffset = aOffset;
}
inline void ResetCaretOffset()
{
mCaretOffset = -1;
mAccWithCaret = nullptr;
}
protected:
SelectionManager();
/**
* Process DOM selection change. Fire selection and caret move events.
*/
void ProcessSelectionChanged(SelData* aSelData);
private:
int32_t mCaretOffset;
HyperTextAccessible* mAccWithCaret;
// Currently focused controls.
nsWeakPtr mCurrCtrlNormalSel;
nsWeakPtr mCurrCtrlSpellSel;
};
} // namespace a11y
} // namespace mozilla
#endif

285
accessible/base/States.h Normal file
View file

@ -0,0 +1,285 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set expandtab shiftwidth=2 tabstop=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/. */
#ifndef _states_h_
#define _states_h_
#include <stdint.h>
namespace mozilla {
namespace a11y {
namespace states {
/**
* The object is disabled, opposite to enabled and sensitive.
*/
const uint64_t UNAVAILABLE = ((uint64_t) 0x1) << 0;
/**
* The object is selected.
*/
const uint64_t SELECTED = ((uint64_t) 0x1) << 1;
/**
* The object has the keyboard focus.
*/
const uint64_t FOCUSED = ((uint64_t) 0x1) << 2;
/**
* The object is pressed.
*/
const uint64_t PRESSED = ((uint64_t) 0x1) << 3;
/**
* The checkable object is checked, applied to check box controls,
* @see CHECKABLE and MIXED states.
*/
const uint64_t CHECKED = ((uint64_t) 0x1) << 4;
/**
* Indicates that the state of a three-state check box or tool bar button is
* undetermined. The check box is neither checked or unchecked, and is
* in the third or mixed state.
*/
const uint64_t MIXED = ((uint64_t) 0x1) << 5;
/**
* The object is designated read-only, so it can't be edited.
*/
const uint64_t READONLY = ((uint64_t) 0x1) << 6;
/**
* The object is hot-tracked by the mouse, which means that its appearance
* has changed to indicate that the mouse pointer is located over it.
*
* This is currently unused.
*/
const uint64_t HOTTRACKED = ((uint64_t) 0x1) << 7;
/**
* This object is the default button in a window.
*/
const uint64_t DEFAULT = ((uint64_t) 0x1) << 8;
/**
* The expandable object's children are displayed, the opposite of collapsed,
* applied to trees, list and other controls.
* @see COLLAPSED state
*/
const uint64_t EXPANDED = ((uint64_t) 0x1) << 9;
/**
* The expandable object's children are not displayed, the opposite of
* expanded, applied to tree lists and other controls,
* @see EXPANDED state.
*/
const uint64_t COLLAPSED = ((uint64_t) 0x1) << 10;
/**
* The control or document can not accept input at this time.
*/
const uint64_t BUSY = ((uint64_t) 0x1) << 11;
/**
* The object is out of normal flow, may be outside of boundaries of its
* parent.
*/
const uint64_t FLOATING = ((uint64_t) 0x1) << 12;
/**
* The object can be checked.
*/
const uint64_t CHECKABLE = ((uint64_t) 0x1) << 13;
/**
* This object is a graphic which is rapidly changing appearance.
*/
const uint64_t ANIMATED = ((uint64_t) 0x1) << 14;
/**
* The object is programmatically hidden.
* So user action like scrolling or switching tabs won't make this visible.
*/
const uint64_t INVISIBLE = ((uint64_t) 0x1) << 15;
/**
* The object is scrolled off screen.
* User action such as scrolling or changing tab may make the object
* visible.
*/
const uint64_t OFFSCREEN = ((uint64_t) 0x1) << 16;
/**
* The object can be resized.
*/
const uint64_t SIZEABLE = ((uint64_t) 0x1) << 17;
/**
* The object can be moved to a different position.
*/
const uint64_t MOVEABLE = ((uint64_t) 0x1) << 18;
/**
* The object describes itself with speech.
* Other speech related assistive technology may want to avoid speaking
* information about this object, because the object is already doing this.
*/
const uint64_t SELFVOICING = ((uint64_t) 0x1) << 19;
/**
* The object can have the focus and become focused.
*/
const uint64_t FOCUSABLE = ((uint64_t) 0x1) << 20;
/**
* The object can be selected.
*/
const uint64_t SELECTABLE = ((uint64_t) 0x1) << 21;
/**
* This object is a link.
*/
const uint64_t LINKED = ((uint64_t) 0x1) << 22;
/**
* This is used for links that have been traversed
* i.e. the linked page has been visited.
*/
const uint64_t TRAVERSED = ((uint64_t) 0x1) << 23;
/**
* Supports multiple selection.
*/
const uint64_t MULTISELECTABLE = ((uint64_t) 0x1) << 24;
/**
* Supports extended selection.
* All objects supporting this are also multipselectable.
* This only makes sense for msaa see bug 635690.
*/
const uint64_t EXTSELECTABLE = ((uint64_t) 0x1) << 25;
/**
* The user is required to interact with this object.
*/
const uint64_t REQUIRED = ((uint64_t) 0x1) << 26;
/**
* The object is an alert, notifying the user of something important.
*/
const uint64_t ALERT = ((uint64_t) 0x1) << 27;
/**
* Used for text fields containing invalid values.
*/
const uint64_t INVALID = ((uint64_t) 0x1) << 28;
/**
* The controls value can not be obtained, and is returned as a set of "*"s.
*/
const uint64_t PROTECTED = ((uint64_t) 0x1) << 29;
/**
* The object can be invoked to show a pop up menu or window.
*/
const uint64_t HASPOPUP = ((uint64_t) 0x1) << 30;
/**
* The editable area has some kind of autocompletion.
*/
const uint64_t SUPPORTS_AUTOCOMPLETION = ((uint64_t) 0x1) << 31;
/**
* The object is no longer available to be queried.
*/
const uint64_t DEFUNCT = ((uint64_t) 0x1) << 32;
/**
* The text is selectable, the object must implement the text interface.
*/
const uint64_t SELECTABLE_TEXT = ((uint64_t) 0x1) << 33;
/**
* The text in this object can be edited.
*/
const uint64_t EDITABLE = ((uint64_t) 0x1) << 34;
/**
* This window is currently the active window.
*/
const uint64_t ACTIVE = ((uint64_t) 0x1) << 35;
/**
* Indicates that the object is modal. Modal objects have the behavior
* that something must be done with the object before the user can
* interact with an object in a different window.
*/
const uint64_t MODAL = ((uint64_t) 0x1) << 36;
/**
* Edit control that can take multiple lines.
*/
const uint64_t MULTI_LINE = ((uint64_t) 0x1) << 37;
/**
* Uses horizontal layout.
*/
const uint64_t HORIZONTAL = ((uint64_t) 0x1) << 38;
/**
* Indicates this object paints every pixel within its rectangular region.
*/
const uint64_t OPAQUE1 = ((uint64_t) 0x1) << 39;
/**
* This text object can only contain 1 line of text.
*/
const uint64_t SINGLE_LINE = ((uint64_t) 0x1) << 40;
/**
* The parent object manages descendants, and this object may only exist
* while it is visible or has focus.
* For example the focused cell of a table or the current element of a list box may have this state.
*/
const uint64_t TRANSIENT = ((uint64_t) 0x1) << 41;
/**
* Uses vertical layout.
* Especially used for sliders and scrollbars.
*/
const uint64_t VERTICAL = ((uint64_t) 0x1) << 42;
/**
* Object not dead, but not up-to-date either.
*/
const uint64_t STALE = ((uint64_t) 0x1) << 43;
/**
* A widget that is not unavailable.
*/
const uint64_t ENABLED = ((uint64_t) 0x1) << 44;
/**
* Same as ENABLED state for now see bug 636158
*/
const uint64_t SENSITIVE = ((uint64_t) 0x1) << 45;
/**
* The object is expandable, provides a UI to expand/collapse its children
* @see EXPANDED and COLLAPSED states.
*/
const uint64_t EXPANDABLE = ((uint64_t) 0x1) << 46;
/**
* The object is pinned, usually indicating it is fixed in place and has permanence.
*/
const uint64_t PINNED = ((uint64_t) 0x1) << 47;
} // namespace states
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,39 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef A11Y_STATISTICS_H_
#define A11Y_STATISTICS_H_
#include "mozilla/Telemetry.h"
namespace mozilla {
namespace a11y {
namespace statistics {
inline void A11yInitialized()
{ Telemetry::Accumulate(Telemetry::A11Y_INSTANTIATED_FLAG, true); }
inline void A11yConsumers(uint32_t aConsumer)
{ Telemetry::Accumulate(Telemetry::A11Y_CONSUMERS, aConsumer); }
/**
* Report that ISimpleDOM* has been used.
*/
inline void ISimpleDOMUsed()
{ Telemetry::Accumulate(Telemetry::A11Y_ISIMPLEDOM_USAGE_FLAG, true); }
/**
* Report that IAccessibleTable has been used.
*/
inline void IAccessibleTableUsed()
{ Telemetry::Accumulate(Telemetry::A11Y_IATABLE_USAGE_FLAG, true); }
} // namespace statistics
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,122 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set expandtab shiftwidth=2 tabstop=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 "StyleInfo.h"
#include "mozilla/dom/Element.h"
#include "nsComputedDOMStyle.h"
#include "nsCSSProps.h"
#include "nsIFrame.h"
using namespace mozilla;
using namespace mozilla::a11y;
StyleInfo::StyleInfo(dom::Element* aElement, nsIPresShell* aPresShell) :
mElement(aElement)
{
mStyleContext =
nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement,
nullptr,
aPresShell);
}
void
StyleInfo::Display(nsAString& aValue)
{
aValue.Truncate();
AppendASCIItoUTF16(
nsCSSProps::ValueToKeyword(mStyleContext->StyleDisplay()->mDisplay,
nsCSSProps::kDisplayKTable), aValue);
}
void
StyleInfo::TextAlign(nsAString& aValue)
{
aValue.Truncate();
AppendASCIItoUTF16(
nsCSSProps::ValueToKeyword(mStyleContext->StyleText()->mTextAlign,
nsCSSProps::kTextAlignKTable), aValue);
}
void
StyleInfo::TextIndent(nsAString& aValue)
{
aValue.Truncate();
const nsStyleCoord& styleCoord =
mStyleContext->StyleText()->mTextIndent;
nscoord coordVal = 0;
switch (styleCoord.GetUnit()) {
case eStyleUnit_Coord:
coordVal = styleCoord.GetCoordValue();
aValue.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(coordVal));
aValue.AppendLiteral("px");
break;
case eStyleUnit_Percent:
aValue.AppendFloat(styleCoord.GetPercentValue() * 100);
aValue.AppendLiteral("%");
break;
case eStyleUnit_Null:
case eStyleUnit_Normal:
case eStyleUnit_Auto:
case eStyleUnit_None:
case eStyleUnit_Factor:
case eStyleUnit_Degree:
case eStyleUnit_Grad:
case eStyleUnit_Radian:
case eStyleUnit_Turn:
case eStyleUnit_FlexFraction:
case eStyleUnit_Integer:
case eStyleUnit_Enumerated:
case eStyleUnit_Calc:
aValue.AppendLiteral("0px");
break;
}
}
void
StyleInfo::Margin(css::Side aSide, nsAString& aValue)
{
MOZ_ASSERT(mElement->GetPrimaryFrame(), " mElement->GetPrimaryFrame() needs to be valid pointer");
aValue.Truncate();
nscoord coordVal = mElement->GetPrimaryFrame()->GetUsedMargin().Side(aSide);
aValue.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(coordVal));
aValue.AppendLiteral("px");
}
void
StyleInfo::FormatColor(const nscolor& aValue, nsString& aFormattedValue)
{
// Combine the string like rgb(R, G, B) from nscolor.
aFormattedValue.AppendLiteral("rgb(");
aFormattedValue.AppendInt(NS_GET_R(aValue));
aFormattedValue.AppendLiteral(", ");
aFormattedValue.AppendInt(NS_GET_G(aValue));
aFormattedValue.AppendLiteral(", ");
aFormattedValue.AppendInt(NS_GET_B(aValue));
aFormattedValue.Append(')');
}
void
StyleInfo::FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue)
{
nsCSSKeyword keyword =
nsCSSProps::ValueToKeywordEnum(aValue, nsCSSProps::kFontStyleKTable);
AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(keyword), aFormattedValue);
}
void
StyleInfo::FormatTextDecorationStyle(uint8_t aValue, nsAString& aFormattedValue)
{
nsCSSKeyword keyword =
nsCSSProps::ValueToKeywordEnum(aValue,
nsCSSProps::kTextDecorationStyleKTable);
AppendUTF8toUTF16(nsCSSKeywords::GetStringValue(keyword), aFormattedValue);
}

View file

@ -0,0 +1,48 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set expandtab shiftwidth=2 tabstop=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/. */
#ifndef _mozilla_a11y_style_h_
#define _mozilla_a11y_style_h_
#include "mozilla/gfx/Types.h"
#include "nsStyleContext.h"
namespace mozilla {
namespace a11y {
class StyleInfo
{
public:
StyleInfo(dom::Element* aElement, nsIPresShell* aPresShell);
~StyleInfo() { }
void Display(nsAString& aValue);
void TextAlign(nsAString& aValue);
void TextIndent(nsAString& aValue);
void MarginLeft(nsAString& aValue) { Margin(eSideLeft, aValue); }
void MarginRight(nsAString& aValue) { Margin(eSideRight, aValue); }
void MarginTop(nsAString& aValue) { Margin(eSideTop, aValue); }
void MarginBottom(nsAString& aValue) { Margin(eSideBottom, aValue); }
static void FormatColor(const nscolor& aValue, nsString& aFormattedValue);
static void FormatFontStyle(const nscoord& aValue, nsAString& aFormattedValue);
static void FormatTextDecorationStyle(uint8_t aValue, nsAString& aFormattedValue);
private:
StyleInfo() = delete;
StyleInfo(const StyleInfo&) = delete;
StyleInfo& operator = (const StyleInfo&) = delete;
void Margin(Side aSide, nsAString& aValue);
dom::Element* mElement;
RefPtr<nsStyleContext> mStyleContext;
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,913 @@
/* -*- 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 "TextAttrs.h"
#include "Accessible-inl.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "StyleInfo.h"
#include "gfxFont.h"
#include "nsFontMetrics.h"
#include "nsLayoutUtils.h"
#include "nsContainerFrame.h"
#include "HyperTextAccessible.h"
#include "mozilla/AppUnits.h"
#include "mozilla/gfx/2D.h"
#if defined(MOZ_WIDGET_GTK)
#include "gfxPlatformGtk.h" // xxx - for UseFcFontList
#endif
using namespace mozilla;
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// TextAttrsMgr
////////////////////////////////////////////////////////////////////////////////
void
TextAttrsMgr::GetAttributes(nsIPersistentProperties* aAttributes,
uint32_t* aStartOffset,
uint32_t* aEndOffset)
{
// 1. Hyper text accessible must be specified always.
// 2. Offset accessible and result hyper text offsets must be specified in
// the case of text attributes.
// 3. Offset accessible and result hyper text offsets must not be specified
// but include default text attributes flag and attributes list must be
// specified in the case of default text attributes.
NS_PRECONDITION(mHyperTextAcc &&
((mOffsetAcc && mOffsetAccIdx != -1 &&
aStartOffset && aEndOffset) ||
(!mOffsetAcc && mOffsetAccIdx == -1 &&
!aStartOffset && !aEndOffset &&
mIncludeDefAttrs && aAttributes)),
"Wrong usage of TextAttrsMgr!");
// Embedded objects are combined into own range with empty attributes set.
if (mOffsetAcc && !mOffsetAcc->IsText()) {
for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) {
Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
if (currAcc->IsText())
break;
(*aStartOffset)--;
}
uint32_t childCount = mHyperTextAcc->ChildCount();
for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childCount;
childIdx++) {
Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
if (currAcc->IsText())
break;
(*aEndOffset)++;
}
return;
}
// Get the content and frame of the accessible. In the case of document
// accessible it's role content and root frame.
nsIContent* hyperTextElm = mHyperTextAcc->GetContent();
if (!hyperTextElm)
return; // XXX: we don't support text attrs on document with no body
nsIFrame* rootFrame = mHyperTextAcc->GetFrame();
MOZ_ASSERT(rootFrame, "No frame for accessible!");
if (!rootFrame)
return;
nsIContent *offsetNode = nullptr, *offsetElm = nullptr;
nsIFrame *frame = nullptr;
if (mOffsetAcc) {
offsetNode = mOffsetAcc->GetContent();
offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode);
MOZ_ASSERT(offsetElm, "No element for offset accessible!");
if (!offsetElm)
return;
frame = offsetElm->GetPrimaryFrame();
}
// "language" text attribute
LangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode);
// "aria-invalid" text attribute
InvalidTextAttr invalidTextAttr(hyperTextElm, offsetNode);
// "background-color" text attribute
BGColorTextAttr bgColorTextAttr(rootFrame, frame);
// "color" text attribute
ColorTextAttr colorTextAttr(rootFrame, frame);
// "font-family" text attribute
FontFamilyTextAttr fontFamilyTextAttr(rootFrame, frame);
// "font-size" text attribute
FontSizeTextAttr fontSizeTextAttr(rootFrame, frame);
// "font-style" text attribute
FontStyleTextAttr fontStyleTextAttr(rootFrame, frame);
// "font-weight" text attribute
FontWeightTextAttr fontWeightTextAttr(rootFrame, frame);
// "auto-generated" text attribute
AutoGeneratedTextAttr autoGenTextAttr(mHyperTextAcc, mOffsetAcc);
// "text-underline(line-through)-style(color)" text attributes
TextDecorTextAttr textDecorTextAttr(rootFrame, frame);
// "text-position" text attribute
TextPosTextAttr textPosTextAttr(rootFrame, frame);
TextAttr* attrArray[] =
{
&langTextAttr,
&invalidTextAttr,
&bgColorTextAttr,
&colorTextAttr,
&fontFamilyTextAttr,
&fontSizeTextAttr,
&fontStyleTextAttr,
&fontWeightTextAttr,
&autoGenTextAttr,
&textDecorTextAttr,
&textPosTextAttr
};
// Expose text attributes if applicable.
if (aAttributes) {
for (uint32_t idx = 0; idx < ArrayLength(attrArray); idx++)
attrArray[idx]->Expose(aAttributes, mIncludeDefAttrs);
}
// Expose text attributes range where they are applied if applicable.
if (mOffsetAcc)
GetRange(attrArray, ArrayLength(attrArray), aStartOffset, aEndOffset);
}
void
TextAttrsMgr::GetRange(TextAttr* aAttrArray[], uint32_t aAttrArrayLen,
uint32_t* aStartOffset, uint32_t* aEndOffset)
{
// Navigate backward from anchor accessible to find start offset.
for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) {
Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
// Stop on embedded accessible since embedded accessibles are combined into
// own range.
if (!currAcc->IsText())
break;
MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()),
"Text accessible has to have an associated DOM element");
bool offsetFound = false;
for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
TextAttr* textAttr = aAttrArray[attrIdx];
if (!textAttr->Equal(currAcc)) {
offsetFound = true;
break;
}
}
if (offsetFound)
break;
*(aStartOffset) -= nsAccUtils::TextLength(currAcc);
}
// Navigate forward from anchor accessible to find end offset.
uint32_t childLen = mHyperTextAcc->ChildCount();
for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childLen; childIdx++) {
Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
if (!currAcc->IsText())
break;
MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()),
"Text accessible has to have an associated DOM element");
bool offsetFound = false;
for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
TextAttr* textAttr = aAttrArray[attrIdx];
// Alter the end offset when text attribute changes its value and stop
// the search.
if (!textAttr->Equal(currAcc)) {
offsetFound = true;
break;
}
}
if (offsetFound)
break;
(*aEndOffset) += nsAccUtils::TextLength(currAcc);
}
}
////////////////////////////////////////////////////////////////////////////////
// LangTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::LangTextAttr::
LangTextAttr(HyperTextAccessible* aRoot,
nsIContent* aRootElm, nsIContent* aElm) :
TTextAttr<nsString>(!aElm), mRootContent(aRootElm)
{
aRoot->Language(mRootNativeValue);
mIsRootDefined = !mRootNativeValue.IsEmpty();
if (aElm) {
nsCoreUtils::GetLanguageFor(aElm, mRootContent, mNativeValue);
mIsDefined = !mNativeValue.IsEmpty();
}
}
TextAttrsMgr::LangTextAttr::
~LangTextAttr() {}
bool
TextAttrsMgr::LangTextAttr::
GetValueFor(Accessible* aAccessible, nsString* aValue)
{
nsCoreUtils::GetLanguageFor(aAccessible->GetContent(), mRootContent, *aValue);
return !aValue->IsEmpty();
}
void
TextAttrsMgr::LangTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const nsString& aValue)
{
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::language, aValue);
}
////////////////////////////////////////////////////////////////////////////////
// InvalidTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::InvalidTextAttr::
InvalidTextAttr(nsIContent* aRootElm, nsIContent* aElm) :
TTextAttr<uint32_t>(!aElm), mRootElm(aRootElm)
{
mIsRootDefined = GetValue(mRootElm, &mRootNativeValue);
if (aElm)
mIsDefined = GetValue(aElm, &mNativeValue);
}
bool
TextAttrsMgr::InvalidTextAttr::
GetValueFor(Accessible* aAccessible, uint32_t* aValue)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
return elm ? GetValue(elm, aValue) : false;
}
void
TextAttrsMgr::InvalidTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const uint32_t& aValue)
{
switch (aValue) {
case eFalse:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
NS_LITERAL_STRING("false"));
break;
case eGrammar:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
NS_LITERAL_STRING("grammar"));
break;
case eSpelling:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
NS_LITERAL_STRING("spelling"));
break;
case eTrue:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::invalid,
NS_LITERAL_STRING("true"));
break;
}
}
bool
TextAttrsMgr::InvalidTextAttr::
GetValue(nsIContent* aElm, uint32_t* aValue)
{
nsIContent* elm = aElm;
do {
if (nsAccUtils::HasDefinedARIAToken(elm, nsGkAtoms::aria_invalid)) {
static nsIContent::AttrValuesArray tokens[] =
{ &nsGkAtoms::_false, &nsGkAtoms::grammar, &nsGkAtoms::spelling,
nullptr };
int32_t idx = elm->FindAttrValueIn(kNameSpaceID_None,
nsGkAtoms::aria_invalid, tokens,
eCaseMatters);
switch (idx) {
case 0:
*aValue = eFalse;
return true;
case 1:
*aValue = eGrammar;
return true;
case 2:
*aValue = eSpelling;
return true;
default:
*aValue = eTrue;
return true;
}
}
} while ((elm = elm->GetParent()) && elm != mRootElm);
return false;
}
////////////////////////////////////////////////////////////////////////////////
// BGColorTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::BGColorTextAttr::
BGColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<nscolor>(!aFrame), mRootFrame(aRootFrame)
{
mIsRootDefined = GetColor(mRootFrame, &mRootNativeValue);
if (aFrame)
mIsDefined = GetColor(aFrame, &mNativeValue);
}
bool
TextAttrsMgr::BGColorTextAttr::
GetValueFor(Accessible* aAccessible, nscolor* aValue)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
if (elm) {
nsIFrame* frame = elm->GetPrimaryFrame();
if (frame) {
return GetColor(frame, aValue);
}
}
return false;
}
void
TextAttrsMgr::BGColorTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const nscolor& aValue)
{
nsAutoString formattedValue;
StyleInfo::FormatColor(aValue, formattedValue);
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::backgroundColor,
formattedValue);
}
bool
TextAttrsMgr::BGColorTextAttr::
GetColor(nsIFrame* aFrame, nscolor* aColor)
{
const nsStyleBackground* styleBackground = aFrame->StyleBackground();
if (NS_GET_A(styleBackground->mBackgroundColor) > 0) {
*aColor = styleBackground->mBackgroundColor;
return true;
}
nsContainerFrame *parentFrame = aFrame->GetParent();
if (!parentFrame) {
*aColor = aFrame->PresContext()->DefaultBackgroundColor();
return true;
}
// Each frame of parents chain for the initially passed 'aFrame' has
// transparent background color. So background color isn't changed from
// 'mRootFrame' to initially passed 'aFrame'.
if (parentFrame == mRootFrame)
return false;
return GetColor(parentFrame, aColor);
}
////////////////////////////////////////////////////////////////////////////////
// ColorTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::ColorTextAttr::
ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<nscolor>(!aFrame)
{
mRootNativeValue = aRootFrame->StyleColor()->mColor;
mIsRootDefined = true;
if (aFrame) {
mNativeValue = aFrame->StyleColor()->mColor;
mIsDefined = true;
}
}
bool
TextAttrsMgr::ColorTextAttr::
GetValueFor(Accessible* aAccessible, nscolor* aValue)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
if (elm) {
nsIFrame* frame = elm->GetPrimaryFrame();
if (frame) {
*aValue = frame->StyleColor()->mColor;
return true;
}
}
return false;
}
void
TextAttrsMgr::ColorTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const nscolor& aValue)
{
nsAutoString formattedValue;
StyleInfo::FormatColor(aValue, formattedValue);
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::color, formattedValue);
}
////////////////////////////////////////////////////////////////////////////////
// FontFamilyTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::FontFamilyTextAttr::
FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<nsString>(!aFrame)
{
mIsRootDefined = GetFontFamily(aRootFrame, mRootNativeValue);
if (aFrame)
mIsDefined = GetFontFamily(aFrame, mNativeValue);
}
bool
TextAttrsMgr::FontFamilyTextAttr::
GetValueFor(Accessible* aAccessible, nsString* aValue)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
if (elm) {
nsIFrame* frame = elm->GetPrimaryFrame();
if (frame) {
return GetFontFamily(frame, *aValue);
}
}
return false;
}
void
TextAttrsMgr::FontFamilyTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const nsString& aValue)
{
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_family, aValue);
}
bool
TextAttrsMgr::FontFamilyTextAttr::
GetFontFamily(nsIFrame* aFrame, nsString& aFamily)
{
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
gfxFont* font = fontGroup->GetFirstValidFont();
gfxFontEntry* fontEntry = font->GetFontEntry();
aFamily = fontEntry->FamilyName();
return true;
}
////////////////////////////////////////////////////////////////////////////////
// FontSizeTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::FontSizeTextAttr::
FontSizeTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<nscoord>(!aFrame)
{
mDC = aRootFrame->PresContext()->DeviceContext();
mRootNativeValue = aRootFrame->StyleFont()->mSize;
mIsRootDefined = true;
if (aFrame) {
mNativeValue = aFrame->StyleFont()->mSize;
mIsDefined = true;
}
}
bool
TextAttrsMgr::FontSizeTextAttr::
GetValueFor(Accessible* aAccessible, nscoord* aValue)
{
nsIContent* el = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
if (el) {
nsIFrame* frame = el->GetPrimaryFrame();
if (frame) {
*aValue = frame->StyleFont()->mSize;
return true;
}
}
return false;
}
void
TextAttrsMgr::FontSizeTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const nscoord& aValue)
{
// Convert from nscoord to pt.
//
// Note: according to IA2, "The conversion doesn't have to be exact.
// The intent is to give the user a feel for the size of the text."
//
// ATK does not specify a unit and will likely follow IA2 here.
//
// XXX todo: consider sharing this code with layout module? (bug 474621)
float px =
NSAppUnitsToFloatPixels(aValue, mozilla::AppUnitsPerCSSPixel());
// Each pt is 4/3 of a CSS pixel.
int pts = NS_lround(px*3/4);
nsAutoString value;
value.AppendInt(pts);
value.AppendLiteral("pt");
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_size, value);
}
////////////////////////////////////////////////////////////////////////////////
// FontStyleTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::FontStyleTextAttr::
FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<nscoord>(!aFrame)
{
mRootNativeValue = aRootFrame->StyleFont()->mFont.style;
mIsRootDefined = true;
if (aFrame) {
mNativeValue = aFrame->StyleFont()->mFont.style;
mIsDefined = true;
}
}
bool
TextAttrsMgr::FontStyleTextAttr::
GetValueFor(Accessible* aAccessible, nscoord* aValue)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
if (elm) {
nsIFrame* frame = elm->GetPrimaryFrame();
if (frame) {
*aValue = frame->StyleFont()->mFont.style;
return true;
}
}
return false;
}
void
TextAttrsMgr::FontStyleTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const nscoord& aValue)
{
nsAutoString formattedValue;
StyleInfo::FormatFontStyle(aValue, formattedValue);
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_style, formattedValue);
}
////////////////////////////////////////////////////////////////////////////////
// FontWeightTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::FontWeightTextAttr::
FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<int32_t>(!aFrame)
{
mRootNativeValue = GetFontWeight(aRootFrame);
mIsRootDefined = true;
if (aFrame) {
mNativeValue = GetFontWeight(aFrame);
mIsDefined = true;
}
}
bool
TextAttrsMgr::FontWeightTextAttr::
GetValueFor(Accessible* aAccessible, int32_t* aValue)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
if (elm) {
nsIFrame* frame = elm->GetPrimaryFrame();
if (frame) {
*aValue = GetFontWeight(frame);
return true;
}
}
return false;
}
void
TextAttrsMgr::FontWeightTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const int32_t& aValue)
{
nsAutoString formattedValue;
formattedValue.AppendInt(aValue);
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::fontWeight, formattedValue);
}
int32_t
TextAttrsMgr::FontWeightTextAttr::
GetFontWeight(nsIFrame* aFrame)
{
// nsFont::width isn't suitable here because it's necessary to expose real
// value of font weight (used font might not have some font weight values).
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForFrame(aFrame, 1.0f);
gfxFontGroup *fontGroup = fm->GetThebesFontGroup();
gfxFont *font = fontGroup->GetFirstValidFont();
// When there doesn't exist a bold font in the family and so the rendering of
// a non-bold font face is changed so that the user sees what looks like a
// bold font, i.e. synthetic bolding is used. IsSyntheticBold method is only
// needed on Mac, but it is "safe" to use on all platforms. (For non-Mac
// platforms it always return false.)
if (font->IsSyntheticBold())
return 700;
bool useFontEntryWeight = true;
// Under Linux, when gfxPangoFontGroup code is used,
// font->GetStyle()->weight will give the absolute weight requested of the
// font face. The gfxPangoFontGroup code uses the gfxFontEntry constructor
// which doesn't initialize the weight field.
#if defined(MOZ_WIDGET_GTK)
useFontEntryWeight = gfxPlatformGtk::UseFcFontList();
#endif
if (useFontEntryWeight) {
// On Windows, font->GetStyle()->weight will give the same weight as
// fontEntry->Weight(), the weight of the first font in the font group,
// which may not be the weight of the font face used to render the
// characters. On Mac, font->GetStyle()->weight will just give the same
// number as getComputedStyle(). fontEntry->Weight() will give the weight
// of the font face used.
gfxFontEntry *fontEntry = font->GetFontEntry();
return fontEntry->Weight();
} else {
return font->GetStyle()->weight;
}
}
////////////////////////////////////////////////////////////////////////////////
// AutoGeneratedTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::AutoGeneratedTextAttr::
AutoGeneratedTextAttr(HyperTextAccessible* aHyperTextAcc,
Accessible* aAccessible) :
TTextAttr<bool>(!aAccessible)
{
mRootNativeValue = false;
mIsRootDefined = false;
if (aAccessible)
mIsDefined = mNativeValue = (aAccessible->NativeRole() == roles::STATICTEXT);
}
bool
TextAttrsMgr::AutoGeneratedTextAttr::
GetValueFor(Accessible* aAccessible, bool* aValue)
{
return *aValue = (aAccessible->NativeRole() == roles::STATICTEXT);
}
void
TextAttrsMgr::AutoGeneratedTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const bool& aValue)
{
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::auto_generated,
aValue ? NS_LITERAL_STRING("true") : NS_LITERAL_STRING("false"));
}
////////////////////////////////////////////////////////////////////////////////
// TextDecorTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::TextDecorValue::
TextDecorValue(nsIFrame* aFrame)
{
const nsStyleTextReset* textReset = aFrame->StyleTextReset();
mStyle = textReset->mTextDecorationStyle;
mColor = aFrame->StyleColor()->
CalcComplexColor(textReset->mTextDecorationColor);
mLine = textReset->mTextDecorationLine &
(NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH);
}
TextAttrsMgr::TextDecorTextAttr::
TextDecorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<TextDecorValue>(!aFrame)
{
mRootNativeValue = TextDecorValue(aRootFrame);
mIsRootDefined = mRootNativeValue.IsDefined();
if (aFrame) {
mNativeValue = TextDecorValue(aFrame);
mIsDefined = mNativeValue.IsDefined();
}
}
bool
TextAttrsMgr::TextDecorTextAttr::
GetValueFor(Accessible* aAccessible, TextDecorValue* aValue)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
if (elm) {
nsIFrame* frame = elm->GetPrimaryFrame();
if (frame) {
*aValue = TextDecorValue(frame);
return aValue->IsDefined();
}
}
return false;
}
void
TextAttrsMgr::TextDecorTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const TextDecorValue& aValue)
{
if (aValue.IsUnderline()) {
nsAutoString formattedStyle;
StyleInfo::FormatTextDecorationStyle(aValue.Style(), formattedStyle);
nsAccUtils::SetAccAttr(aAttributes,
nsGkAtoms::textUnderlineStyle,
formattedStyle);
nsAutoString formattedColor;
StyleInfo::FormatColor(aValue.Color(), formattedColor);
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textUnderlineColor,
formattedColor);
return;
}
if (aValue.IsLineThrough()) {
nsAutoString formattedStyle;
StyleInfo::FormatTextDecorationStyle(aValue.Style(), formattedStyle);
nsAccUtils::SetAccAttr(aAttributes,
nsGkAtoms::textLineThroughStyle,
formattedStyle);
nsAutoString formattedColor;
StyleInfo::FormatColor(aValue.Color(), formattedColor);
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textLineThroughColor,
formattedColor);
}
}
////////////////////////////////////////////////////////////////////////////////
// TextPosTextAttr
////////////////////////////////////////////////////////////////////////////////
TextAttrsMgr::TextPosTextAttr::
TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
TTextAttr<TextPosValue>(!aFrame)
{
mRootNativeValue = GetTextPosValue(aRootFrame);
mIsRootDefined = mRootNativeValue != eTextPosNone;
if (aFrame) {
mNativeValue = GetTextPosValue(aFrame);
mIsDefined = mNativeValue != eTextPosNone;
}
}
bool
TextAttrsMgr::TextPosTextAttr::
GetValueFor(Accessible* aAccessible, TextPosValue* aValue)
{
nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
if (elm) {
nsIFrame* frame = elm->GetPrimaryFrame();
if (frame) {
*aValue = GetTextPosValue(frame);
return *aValue != eTextPosNone;
}
}
return false;
}
void
TextAttrsMgr::TextPosTextAttr::
ExposeValue(nsIPersistentProperties* aAttributes, const TextPosValue& aValue)
{
switch (aValue) {
case eTextPosBaseline:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
NS_LITERAL_STRING("baseline"));
break;
case eTextPosSub:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
NS_LITERAL_STRING("sub"));
break;
case eTextPosSuper:
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::textPosition,
NS_LITERAL_STRING("super"));
break;
case eTextPosNone:
break;
}
}
TextAttrsMgr::TextPosValue
TextAttrsMgr::TextPosTextAttr::
GetTextPosValue(nsIFrame* aFrame) const
{
const nsStyleCoord& styleCoord = aFrame->StyleDisplay()->mVerticalAlign;
switch (styleCoord.GetUnit()) {
case eStyleUnit_Enumerated:
switch (styleCoord.GetIntValue()) {
case NS_STYLE_VERTICAL_ALIGN_BASELINE:
return eTextPosBaseline;
case NS_STYLE_VERTICAL_ALIGN_SUB:
return eTextPosSub;
case NS_STYLE_VERTICAL_ALIGN_SUPER:
return eTextPosSuper;
// No good guess for these:
// NS_STYLE_VERTICAL_ALIGN_TOP
// NS_STYLE_VERTICAL_ALIGN_TEXT_TOP
// NS_STYLE_VERTICAL_ALIGN_MIDDLE
// NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM
// NS_STYLE_VERTICAL_ALIGN_BOTTOM
// NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE
// Do not expose value of text-position attribute.
default:
break;
}
return eTextPosNone;
case eStyleUnit_Percent:
{
float percentValue = styleCoord.GetPercentValue();
return percentValue > 0 ?
eTextPosSuper :
(percentValue < 0 ? eTextPosSub : eTextPosBaseline);
}
case eStyleUnit_Coord:
{
nscoord coordValue = styleCoord.GetCoordValue();
return coordValue > 0 ?
eTextPosSuper :
(coordValue < 0 ? eTextPosSub : eTextPosBaseline);
}
case eStyleUnit_Null:
case eStyleUnit_Normal:
case eStyleUnit_Auto:
case eStyleUnit_None:
case eStyleUnit_Factor:
case eStyleUnit_Degree:
case eStyleUnit_Grad:
case eStyleUnit_Radian:
case eStyleUnit_Turn:
case eStyleUnit_FlexFraction:
case eStyleUnit_Integer:
case eStyleUnit_Calc:
break;
}
const nsIContent* content = aFrame->GetContent();
if (content) {
if (content->IsHTMLElement(nsGkAtoms::sup))
return eTextPosSuper;
if (content->IsHTMLElement(nsGkAtoms::sub))
return eTextPosSub;
}
return eTextPosNone;
}

478
accessible/base/TextAttrs.h Normal file
View file

@ -0,0 +1,478 @@
/* -*- 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/. */
#ifndef nsTextAttrs_h_
#define nsTextAttrs_h_
#include "nsCOMPtr.h"
#include "nsColor.h"
#include "nsStyleConsts.h"
class nsIFrame;
class nsIPersistentProperties;
class nsIContent;
class nsDeviceContext;
namespace mozilla {
namespace a11y {
class Accessible;
class HyperTextAccessible;
/**
* Used to expose text attributes for the hyper text accessible (see
* HyperTextAccessible class).
*
* @note "invalid: spelling" text attribute is implemented entirely in
* HyperTextAccessible class.
*/
class TextAttrsMgr
{
public:
/**
* Constructor. Used to expose default text attributes.
*/
explicit TextAttrsMgr(HyperTextAccessible* aHyperTextAcc) :
mOffsetAcc(nullptr), mHyperTextAcc(aHyperTextAcc),
mOffsetAccIdx(-1), mIncludeDefAttrs(true) { }
/**
* Constructor. Used to expose text attributes at the given offset.
*
* @param aHyperTextAcc [in] hyper text accessible text attributes are
* calculated for
* @param aIncludeDefAttrs [optional] indicates whether default text
* attributes should be included into list of exposed
* text attributes
* @param oOffsetAcc [optional] offset an accessible the text attributes
* should be calculated for
* @param oOffsetAccIdx [optional] index in parent of offset accessible
*/
TextAttrsMgr(HyperTextAccessible* aHyperTextAcc,
bool aIncludeDefAttrs,
Accessible* aOffsetAcc,
int32_t aOffsetAccIdx) :
mOffsetAcc(aOffsetAcc), mHyperTextAcc(aHyperTextAcc),
mOffsetAccIdx(aOffsetAccIdx), mIncludeDefAttrs(aIncludeDefAttrs) { }
/*
* Return text attributes and hyper text offsets where these attributes are
* applied. Offsets are calculated in the case of non default attributes.
*
* @note In the case of default attributes pointers on hyper text offsets
* must be skipped.
*
* @param aAttributes [in, out] text attributes list
* @param aStartHTOffset [out, optional] start hyper text offset
* @param aEndHTOffset [out, optional] end hyper text offset
*/
void GetAttributes(nsIPersistentProperties* aAttributes,
uint32_t* aStartHTOffset = nullptr,
uint32_t* aEndHTOffset = nullptr);
protected:
/**
* Calculates range (start and end offsets) of text where the text attributes
* are stretched. New offsets may be smaller if one of text attributes changes
* its value before or after the given offsets.
*
* @param aTextAttrArray [in] text attributes array
* @param aAttrArrayLen [in] text attributes array length
* @param aStartHTOffset [in, out] the start offset
* @param aEndHTOffset [in, out] the end offset
*/
class TextAttr;
void GetRange(TextAttr* aAttrArray[], uint32_t aAttrArrayLen,
uint32_t* aStartOffset, uint32_t* aEndOffset);
private:
Accessible* mOffsetAcc;
HyperTextAccessible* mHyperTextAcc;
int32_t mOffsetAccIdx;
bool mIncludeDefAttrs;
protected:
/**
* Interface class of text attribute class implementations.
*/
class TextAttr
{
public:
/**
* Expose the text attribute to the given attribute set.
*
* @param aAttributes [in] the given attribute set
* @param aIncludeDefAttrValue [in] if true then attribute is exposed even
* if its value is the same as default one
*/
virtual void Expose(nsIPersistentProperties* aAttributes,
bool aIncludeDefAttrValue) = 0;
/**
* Return true if the text attribute value on the given element equals with
* predefined attribute value.
*/
virtual bool Equal(Accessible* aAccessible) = 0;
};
/**
* Base class to work with text attributes. See derived classes below.
*/
template<class T>
class TTextAttr : public TextAttr
{
public:
explicit TTextAttr(bool aGetRootValue) : mGetRootValue(aGetRootValue) {}
// TextAttr
virtual void Expose(nsIPersistentProperties* aAttributes,
bool aIncludeDefAttrValue) override
{
if (mGetRootValue) {
if (mIsRootDefined)
ExposeValue(aAttributes, mRootNativeValue);
return;
}
if (mIsDefined) {
if (aIncludeDefAttrValue || mRootNativeValue != mNativeValue)
ExposeValue(aAttributes, mNativeValue);
return;
}
if (aIncludeDefAttrValue && mIsRootDefined)
ExposeValue(aAttributes, mRootNativeValue);
}
virtual bool Equal(Accessible* aAccessible) override
{
T nativeValue;
bool isDefined = GetValueFor(aAccessible, &nativeValue);
if (!mIsDefined && !isDefined)
return true;
if (mIsDefined && isDefined)
return nativeValue == mNativeValue;
if (mIsDefined)
return mNativeValue == mRootNativeValue;
return nativeValue == mRootNativeValue;
}
protected:
// Expose the text attribute with the given value to attribute set.
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const T& aValue) = 0;
// Return native value for the given DOM element.
virtual bool GetValueFor(Accessible* aAccessible, T* aValue) = 0;
// Indicates if root value should be exposed.
bool mGetRootValue;
// Native value and flag indicating if the value is defined (initialized in
// derived classes). Note, undefined native value means it is inherited
// from root.
MOZ_INIT_OUTSIDE_CTOR T mNativeValue;
MOZ_INIT_OUTSIDE_CTOR bool mIsDefined;
// Native root value and flag indicating if the value is defined (initialized
// in derived classes).
MOZ_INIT_OUTSIDE_CTOR T mRootNativeValue;
MOZ_INIT_OUTSIDE_CTOR bool mIsRootDefined;
};
/**
* Class is used for the work with 'language' text attribute.
*/
class LangTextAttr : public TTextAttr<nsString>
{
public:
LangTextAttr(HyperTextAccessible* aRoot, nsIContent* aRootElm,
nsIContent* aElm);
virtual ~LangTextAttr();
protected:
// TextAttr
virtual bool GetValueFor(Accessible* aAccessible, nsString* aValue) override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const nsString& aValue) override;
private:
nsCOMPtr<nsIContent> mRootContent;
};
/**
* Class is used for the 'invalid' text attribute. Note, it calculated
* the attribute from aria-invalid attribute only; invalid:spelling attribute
* calculated from misspelled text in the editor is managed by
* HyperTextAccessible and applied on top of the value from aria-invalid.
*/
class InvalidTextAttr : public TTextAttr<uint32_t>
{
public:
InvalidTextAttr(nsIContent* aRootElm, nsIContent* aElm);
virtual ~InvalidTextAttr() { };
protected:
enum {
eFalse,
eGrammar,
eSpelling,
eTrue
};
// TextAttr
virtual bool GetValueFor(Accessible* aAccessible, uint32_t* aValue) override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const uint32_t& aValue) override;
private:
bool GetValue(nsIContent* aElm, uint32_t* aValue);
nsIContent* mRootElm;
};
/**
* Class is used for the work with 'background-color' text attribute.
*/
class BGColorTextAttr : public TTextAttr<nscolor>
{
public:
BGColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~BGColorTextAttr() { }
protected:
// TextAttr
virtual bool GetValueFor(Accessible* aAccessible, nscolor* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const nscolor& aValue) override;
private:
bool GetColor(nsIFrame* aFrame, nscolor* aColor);
nsIFrame* mRootFrame;
};
/**
* Class is used for the work with 'color' text attribute.
*/
class ColorTextAttr : public TTextAttr<nscolor>
{
public:
ColorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~ColorTextAttr() { }
protected:
// TTextAttr
virtual bool GetValueFor(Accessible* aAccessible, nscolor* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const nscolor& aValue) override;
};
/**
* Class is used for the work with "font-family" text attribute.
*/
class FontFamilyTextAttr : public TTextAttr<nsString>
{
public:
FontFamilyTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~FontFamilyTextAttr() { }
protected:
// TTextAttr
virtual bool GetValueFor(Accessible* aAccessible, nsString* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const nsString& aValue) override;
private:
bool GetFontFamily(nsIFrame* aFrame, nsString& aFamily);
};
/**
* Class is used for the work with "font-size" text attribute.
*/
class FontSizeTextAttr : public TTextAttr<nscoord>
{
public:
FontSizeTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~FontSizeTextAttr() { }
protected:
// TTextAttr
virtual bool GetValueFor(Accessible* aAccessible, nscoord* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const nscoord& aValue) override;
private:
nsDeviceContext* mDC;
};
/**
* Class is used for the work with "font-style" text attribute.
*/
class FontStyleTextAttr : public TTextAttr<nscoord>
{
public:
FontStyleTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~FontStyleTextAttr() { }
protected:
// TTextAttr
virtual bool GetValueFor(Accessible* aContent, nscoord* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const nscoord& aValue) override;
};
/**
* Class is used for the work with "font-weight" text attribute.
*/
class FontWeightTextAttr : public TTextAttr<int32_t>
{
public:
FontWeightTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~FontWeightTextAttr() { }
protected:
// TTextAttr
virtual bool GetValueFor(Accessible* aAccessible, int32_t* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const int32_t& aValue) override;
private:
int32_t GetFontWeight(nsIFrame* aFrame);
};
/**
* Class is used for the work with 'auto-generated' text attribute.
*/
class AutoGeneratedTextAttr : public TTextAttr<bool>
{
public:
AutoGeneratedTextAttr(HyperTextAccessible* aHyperTextAcc,
Accessible* aAccessible);
virtual ~AutoGeneratedTextAttr() { }
protected:
// TextAttr
virtual bool GetValueFor(Accessible* aAccessible, bool* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const bool& aValue) override;
};
/**
* TextDecorTextAttr class is used for the work with
* "text-line-through-style", "text-line-through-color",
* "text-underline-style" and "text-underline-color" text attributes.
*/
class TextDecorValue
{
public:
TextDecorValue() { }
explicit TextDecorValue(nsIFrame* aFrame);
nscolor Color() const { return mColor; }
uint8_t Style() const { return mStyle; }
bool IsDefined() const
{ return IsUnderline() || IsLineThrough(); }
bool IsUnderline() const
{ return mLine & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; }
bool IsLineThrough() const
{ return mLine & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; }
bool operator ==(const TextDecorValue& aValue)
{
return mColor == aValue.mColor && mLine == aValue.mLine &&
mStyle == aValue.mStyle;
}
bool operator !=(const TextDecorValue& aValue)
{ return !(*this == aValue); }
private:
nscolor mColor;
uint8_t mLine;
uint8_t mStyle;
};
class TextDecorTextAttr : public TTextAttr<TextDecorValue>
{
public:
TextDecorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~TextDecorTextAttr() { }
protected:
// TextAttr
virtual bool GetValueFor(Accessible* aAccessible, TextDecorValue* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const TextDecorValue& aValue) override;
};
/**
* Class is used for the work with "text-position" text attribute.
*/
enum TextPosValue {
eTextPosNone = 0,
eTextPosBaseline,
eTextPosSub,
eTextPosSuper
};
class TextPosTextAttr : public TTextAttr<TextPosValue>
{
public:
TextPosTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame);
virtual ~TextPosTextAttr() { }
protected:
// TextAttr
virtual bool GetValueFor(Accessible* aAccessible, TextPosValue* aValue)
override;
virtual void ExposeValue(nsIPersistentProperties* aAttributes,
const TextPosValue& aValue) override;
private:
TextPosValue GetTextPosValue(nsIFrame* aFrame) const;
};
}; // TextAttrMgr
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_TextRange_inl_h__
#define mozilla_a11y_TextRange_inl_h__
#include "TextRange.h"
#include "HyperTextAccessible.h"
namespace mozilla {
namespace a11y {
inline Accessible*
TextRange::Container() const
{
uint32_t pos1 = 0, pos2 = 0;
AutoTArray<Accessible*, 30> parents1, parents2;
return CommonParent(mStartContainer, mEndContainer,
&parents1, &pos1, &parents2, &pos2);
}
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,380 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "TextRange-inl.h"
#include "Accessible-inl.h"
#include "nsAccUtils.h"
namespace mozilla {
namespace a11y {
////////////////////////////////////////////////////////////////////////////////
// TextPoint
bool
TextPoint::operator <(const TextPoint& aPoint) const
{
if (mContainer == aPoint.mContainer)
return mOffset < aPoint.mOffset;
// Build the chain of parents
Accessible* p1 = mContainer;
Accessible* p2 = aPoint.mContainer;
AutoTArray<Accessible*, 30> parents1, parents2;
do {
parents1.AppendElement(p1);
p1 = p1->Parent();
} while (p1);
do {
parents2.AppendElement(p2);
p2 = p2->Parent();
} while (p2);
// Find where the parent chain differs
uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
Accessible* child1 = parents1.ElementAt(--pos1);
Accessible* child2 = parents2.ElementAt(--pos2);
if (child1 != child2)
return child1->IndexInParent() < child2->IndexInParent();
}
NS_ERROR("Broken tree?!");
return false;
}
////////////////////////////////////////////////////////////////////////////////
// TextRange
TextRange::TextRange(HyperTextAccessible* aRoot,
HyperTextAccessible* aStartContainer, int32_t aStartOffset,
HyperTextAccessible* aEndContainer, int32_t aEndOffset) :
mRoot(aRoot), mStartContainer(aStartContainer), mEndContainer(aEndContainer),
mStartOffset(aStartOffset), mEndOffset(aEndOffset)
{
}
void
TextRange::EmbeddedChildren(nsTArray<Accessible*>* aChildren) const
{
if (mStartContainer == mEndContainer) {
int32_t startIdx = mStartContainer->GetChildIndexAtOffset(mStartOffset);
int32_t endIdx = mStartContainer->GetChildIndexAtOffset(mEndOffset);
for (int32_t idx = startIdx; idx <= endIdx; idx++) {
Accessible* child = mStartContainer->GetChildAt(idx);
if (!child->IsText()) {
aChildren->AppendElement(child);
}
}
return;
}
Accessible* p1 = mStartContainer->GetChildAtOffset(mStartOffset);
Accessible* p2 = mEndContainer->GetChildAtOffset(mEndOffset);
uint32_t pos1 = 0, pos2 = 0;
AutoTArray<Accessible*, 30> parents1, parents2;
Accessible* container =
CommonParent(p1, p2, &parents1, &pos1, &parents2, &pos2);
// Traverse the tree up to the container and collect embedded objects.
for (uint32_t idx = 0; idx < pos1 - 1; idx++) {
Accessible* parent = parents1[idx + 1];
Accessible* child = parents1[idx];
uint32_t childCount = parent->ChildCount();
for (uint32_t childIdx = child->IndexInParent(); childIdx < childCount; childIdx++) {
Accessible* next = parent->GetChildAt(childIdx);
if (!next->IsText()) {
aChildren->AppendElement(next);
}
}
}
// Traverse through direct children in the container.
int32_t endIdx = parents2[pos2 - 1]->IndexInParent();
int32_t childIdx = parents1[pos1 - 1]->IndexInParent() + 1;
for (; childIdx < endIdx; childIdx++) {
Accessible* next = container->GetChildAt(childIdx);
if (!next->IsText()) {
aChildren->AppendElement(next);
}
}
// Traverse down from the container to end point.
for (int32_t idx = pos2 - 2; idx > 0; idx--) {
Accessible* parent = parents2[idx];
Accessible* child = parents2[idx - 1];
int32_t endIdx = child->IndexInParent();
for (int32_t childIdx = 0; childIdx < endIdx; childIdx++) {
Accessible* next = parent->GetChildAt(childIdx);
if (!next->IsText()) {
aChildren->AppendElement(next);
}
}
}
}
void
TextRange::Text(nsAString& aText) const
{
Accessible* current = mStartContainer->GetChildAtOffset(mStartOffset);
uint32_t startIntlOffset =
mStartOffset - mStartContainer->GetChildOffset(current);
while (current && TextInternal(aText, current, startIntlOffset)) {
current = current->Parent();
if (!current)
break;
current = current->NextSibling();
}
}
void
TextRange::Bounds(nsTArray<nsIntRect> aRects) const
{
}
void
TextRange::Normalize(ETextUnit aUnit)
{
}
bool
TextRange::Crop(Accessible* aContainer)
{
uint32_t boundaryPos = 0, containerPos = 0;
AutoTArray<Accessible*, 30> boundaryParents, containerParents;
// Crop the start boundary.
Accessible* container = nullptr;
Accessible* boundary = mStartContainer->GetChildAtOffset(mStartOffset);
if (boundary != aContainer) {
CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
&containerParents, &containerPos);
if (boundaryPos == 0) {
if (containerPos != 0) {
// The container is contained by the start boundary, reduce the range to
// the point starting at the container.
aContainer->ToTextPoint(mStartContainer.StartAssignment(), &mStartOffset);
static_cast<Accessible*>(mStartContainer)->AddRef();
}
else {
// The start boundary and the container are siblings.
container = aContainer;
}
}
else if (containerPos != 0) {
// The container does not contain the start boundary.
boundary = boundaryParents[boundaryPos];
container = containerParents[containerPos];
}
if (container) {
// If the range start is after the container, then make the range invalid.
if (boundary->IndexInParent() > container->IndexInParent()) {
return !!(mRoot = nullptr);
}
// If the range starts before the container, then reduce the range to
// the point starting at the container.
if (boundary->IndexInParent() < container->IndexInParent()) {
container->ToTextPoint(mStartContainer.StartAssignment(), &mStartOffset);
mStartContainer.get()->AddRef();
}
}
boundaryParents.SetLengthAndRetainStorage(0);
containerParents.SetLengthAndRetainStorage(0);
}
boundary = mEndContainer->GetChildAtOffset(mEndOffset);
if (boundary == aContainer) {
return true;
}
// Crop the end boundary.
container = nullptr;
CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
&containerParents, &containerPos);
if (boundaryPos == 0) {
if (containerPos != 0) {
aContainer->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false);
static_cast<Accessible*>(mEndContainer)->AddRef();
}
else {
container = aContainer;
}
}
else if (containerPos != 0) {
boundary = boundaryParents[boundaryPos];
container = containerParents[containerPos];
}
if (!container) {
return true;
}
if (boundary->IndexInParent() < container->IndexInParent()) {
return !!(mRoot = nullptr);
}
if (boundary->IndexInParent() > container->IndexInParent()) {
container->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false);
static_cast<Accessible*>(mEndContainer)->AddRef();
}
return true;
}
void
TextRange::FindText(const nsAString& aText, EDirection aDirection,
nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const
{
}
void
TextRange::FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
TextRange* aFoundRange) const
{
}
void
TextRange::AddToSelection() const
{
}
void
TextRange::RemoveFromSelection() const
{
}
void
TextRange::Select() const
{
}
void
TextRange::ScrollIntoView(EHowToAlign aHow) const
{
}
////////////////////////////////////////////////////////////////////////////////
// pivate
void
TextRange::Set(HyperTextAccessible* aRoot,
HyperTextAccessible* aStartContainer, int32_t aStartOffset,
HyperTextAccessible* aEndContainer, int32_t aEndOffset)
{
mRoot = aRoot;
mStartContainer = aStartContainer;
mEndContainer = aEndContainer;
mStartOffset = aStartOffset;
mEndOffset = aEndOffset;
}
bool
TextRange::TextInternal(nsAString& aText, Accessible* aCurrent,
uint32_t aStartIntlOffset) const
{
bool moveNext = true;
int32_t endIntlOffset = -1;
if (aCurrent->Parent() == mEndContainer &&
mEndContainer->GetChildAtOffset(mEndOffset) == aCurrent) {
uint32_t currentStartOffset = mEndContainer->GetChildOffset(aCurrent);
endIntlOffset = mEndOffset - currentStartOffset;
if (endIntlOffset == 0)
return false;
moveNext = false;
}
if (aCurrent->IsTextLeaf()) {
aCurrent->AppendTextTo(aText, aStartIntlOffset,
endIntlOffset - aStartIntlOffset);
if (!moveNext)
return false;
}
Accessible* next = aCurrent->FirstChild();
if (next) {
if (!TextInternal(aText, next, 0))
return false;
}
next = aCurrent->NextSibling();
if (next) {
if (!TextInternal(aText, next, 0))
return false;
}
return moveNext;
}
void
TextRange::MoveInternal(ETextUnit aUnit, int32_t aCount,
HyperTextAccessible& aContainer, int32_t aOffset,
HyperTextAccessible* aStopContainer, int32_t aStopOffset)
{
}
Accessible*
TextRange::CommonParent(Accessible* aAcc1, Accessible* aAcc2,
nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
nsTArray<Accessible*>* aParents2, uint32_t* aPos2) const
{
if (aAcc1 == aAcc2) {
return aAcc1;
}
MOZ_ASSERT(aParents1->Length() == 0 || aParents2->Length() == 0,
"Wrong arguments");
// Build the chain of parents.
Accessible* p1 = aAcc1;
Accessible* p2 = aAcc2;
do {
aParents1->AppendElement(p1);
p1 = p1->Parent();
} while (p1);
do {
aParents2->AppendElement(p2);
p2 = p2->Parent();
} while (p2);
// Find where the parent chain differs
*aPos1 = aParents1->Length();
*aPos2 = aParents2->Length();
Accessible* parent = nullptr;
uint32_t len = 0;
for (len = std::min(*aPos1, *aPos2); len > 0; --len) {
Accessible* child1 = aParents1->ElementAt(--(*aPos1));
Accessible* child2 = aParents2->ElementAt(--(*aPos2));
if (child1 != child2)
break;
parent = child1;
}
return parent;
}
} // namespace a11y
} // namespace mozilla

270
accessible/base/TextRange.h Normal file
View file

@ -0,0 +1,270 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef mozilla_a11y_TextRange_h__
#define mozilla_a11y_TextRange_h__
#include "mozilla/Move.h"
#include "nsCaseTreatment.h"
#include "nsRect.h"
#include "nsTArray.h"
class nsIVariant;
namespace mozilla {
namespace a11y {
class Accessible;
class HyperTextAccessible;
/**
* A text point (hyper text + offset), represents a boundary of text range.
*/
struct TextPoint final
{
TextPoint(HyperTextAccessible* aContainer, int32_t aOffset) :
mContainer(aContainer), mOffset(aOffset) { }
TextPoint(const TextPoint& aPoint) :
mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) { }
HyperTextAccessible* mContainer;
int32_t mOffset;
bool operator ==(const TextPoint& aPoint) const
{ return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset; }
bool operator <(const TextPoint& aPoint) const;
};
/**
* Represents a text range within the text control or document.
*/
class TextRange final
{
public:
TextRange(HyperTextAccessible* aRoot,
HyperTextAccessible* aStartContainer, int32_t aStartOffset,
HyperTextAccessible* aEndContainer, int32_t aEndOffset);
TextRange() {}
TextRange(TextRange&& aRange) :
mRoot(mozilla::Move(aRange.mRoot)),
mStartContainer(mozilla::Move(aRange.mStartContainer)),
mEndContainer(mozilla::Move(aRange.mEndContainer)),
mStartOffset(aRange.mStartOffset), mEndOffset(aRange.mEndOffset) {}
TextRange& operator= (TextRange&& aRange)
{
mRoot = mozilla::Move(aRange.mRoot);
mStartContainer = mozilla::Move(aRange.mStartContainer);
mEndContainer = mozilla::Move(aRange.mEndContainer);
mStartOffset = aRange.mStartOffset;
mEndOffset = aRange.mEndOffset;
return *this;
}
HyperTextAccessible* StartContainer() const { return mStartContainer; }
int32_t StartOffset() const { return mStartOffset; }
HyperTextAccessible* EndContainer() const { return mEndContainer; }
int32_t EndOffset() const { return mEndOffset; }
bool operator ==(const TextRange& aRange) const
{
return mStartContainer == aRange.mStartContainer &&
mStartOffset == aRange.mStartOffset &&
mEndContainer == aRange.mEndContainer && mEndOffset == aRange.mEndOffset;
}
TextPoint StartPoint() const { return TextPoint(mStartContainer, mStartOffset); }
TextPoint EndPoint() const { return TextPoint(mEndContainer, mEndOffset); }
/**
* Return a container containing both start and end points.
*/
Accessible* Container() const;
/**
* Return a list of embedded objects enclosed by the text range (includes
* partially overlapped objects).
*/
void EmbeddedChildren(nsTArray<Accessible*>* aChildren) const;
/**
* Return text enclosed by the range.
*/
void Text(nsAString& aText) const;
/**
* Return list of bounding rects of the text range by lines.
*/
void Bounds(nsTArray<nsIntRect> aRects) const;
enum ETextUnit {
eFormat,
eWord,
eLine,
eParagraph,
ePage,
eDocument
};
/**
* Move the range or its points on specified amount of given units.
*/
void Move(ETextUnit aUnit, int32_t aCount)
{
MoveEnd(aUnit, aCount);
MoveStart(aUnit, aCount);
}
void MoveStart(ETextUnit aUnit, int32_t aCount)
{
MoveInternal(aUnit, aCount, *mStartContainer, mStartOffset,
mEndContainer, mEndOffset);
}
void MoveEnd(ETextUnit aUnit, int32_t aCount)
{ MoveInternal(aUnit, aCount, *mEndContainer, mEndOffset); }
/**
* Move the range points to the closest unit boundaries.
*/
void Normalize(ETextUnit aUnit);
/**
* Crops the range if it overlaps the given accessible element boundaries,
* returns true if the range was cropped successfully.
*/
bool Crop(Accessible* aContainer);
enum EDirection {
eBackward,
eForward
};
/**
* Return range enclosing the found text.
*/
void FindText(const nsAString& aText, EDirection aDirection,
nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const;
enum EAttr {
eAnimationStyleAttr,
eAnnotationObjectsAttr,
eAnnotationTypesAttr,
eBackgroundColorAttr,
eBulletStyleAttr,
eCapStyleAttr,
eCaretBidiModeAttr,
eCaretPositionAttr,
eCultureAttr,
eFontNameAttr,
eFontSizeAttr,
eFontWeightAttr,
eForegroundColorAttr,
eHorizontalTextAlignmentAttr,
eIndentationFirstLineAttr,
eIndentationLeadingAttr,
eIndentationTrailingAttr,
eIsActiveAttr,
eIsHiddenAttr,
eIsItalicAttr,
eIsReadOnlyAttr,
eIsSubscriptAttr,
eIsSuperscriptAttr,
eLinkAttr,
eMarginBottomAttr,
eMarginLeadingAttr,
eMarginTopAttr,
eMarginTrailingAttr,
eOutlineStylesAttr,
eOverlineColorAttr,
eOverlineStyleAttr,
eSelectionActiveEndAttr,
eStrikethroughColorAttr,
eStrikethroughStyleAttr,
eStyleIdAttr,
eStyleNameAttr,
eTabsAttr,
eTextFlowDirectionsAttr,
eUnderlineColorAttr,
eUnderlineStyleAttr
};
/**
* Return range enclosing text having requested attribute.
*/
void FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
TextRange* aFoundRange) const;
/**
* Add/remove the text range from selection.
*/
void AddToSelection() const;
void RemoveFromSelection() const;
void Select() const;
/**
* Scroll the text range into view.
*/
enum EHowToAlign {
eAlignToTop,
eAlignToBottom
};
void ScrollIntoView(EHowToAlign aHow) const;
/**
* Return true if this TextRange object represents an actual range of text.
*/
bool IsValid() const { return mRoot; }
void SetStartPoint(HyperTextAccessible* aContainer, int32_t aOffset)
{ mStartContainer = aContainer; mStartOffset = aOffset; }
void SetEndPoint(HyperTextAccessible* aContainer, int32_t aOffset)
{ mStartContainer = aContainer; mStartOffset = aOffset; }
private:
TextRange(const TextRange& aRange) = delete;
TextRange& operator=(const TextRange& aRange) = delete;
friend class HyperTextAccessible;
friend class xpcAccessibleTextRange;
void Set(HyperTextAccessible* aRoot,
HyperTextAccessible* aStartContainer, int32_t aStartOffset,
HyperTextAccessible* aEndContainer, int32_t aEndOffset);
/**
* Text() method helper.
* @param aText [in,out] calculated text
* @param aCurrent [in] currently traversed node
* @param aStartIntlOffset [in] start offset if current node is a text node
* @return true if calculation is not finished yet
*/
bool TextInternal(nsAString& aText, Accessible* aCurrent,
uint32_t aStartIntlOffset) const;
void MoveInternal(ETextUnit aUnit, int32_t aCount,
HyperTextAccessible& aContainer, int32_t aOffset,
HyperTextAccessible* aStopContainer = nullptr,
int32_t aStopOffset = 0);
/**
* A helper method returning a common parent for two given accessible
* elements.
*/
Accessible* CommonParent(Accessible* aAcc1, Accessible* aAcc2,
nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
nsTArray<Accessible*>* aParents2, uint32_t* aPos2) const;
RefPtr<HyperTextAccessible> mRoot;
RefPtr<HyperTextAccessible> mStartContainer;
RefPtr<HyperTextAccessible> mEndContainer;
int32_t mStartOffset;
int32_t mEndOffset;
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,202 @@
/* -*- 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 "TextUpdater.h"
#include "Accessible-inl.h"
#include "DocAccessible-inl.h"
#include "TextLeafAccessible.h"
#include <algorithm>
using namespace mozilla::a11y;
void
TextUpdater::Run(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf,
const nsAString& aNewText)
{
NS_ASSERTION(aTextLeaf, "No text leaf accessible?");
const nsString& oldText = aTextLeaf->Text();
uint32_t oldLen = oldText.Length(), newLen = aNewText.Length();
uint32_t minLen = std::min(oldLen, newLen);
// Skip coinciding begin substrings.
uint32_t skipStart = 0;
for (; skipStart < minLen; skipStart++) {
if (aNewText[skipStart] != oldText[skipStart])
break;
}
// The text was changed. Do update.
if (skipStart != minLen || oldLen != newLen) {
TextUpdater updater(aDocument, aTextLeaf);
updater.DoUpdate(aNewText, oldText, skipStart);
}
}
void
TextUpdater::DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
uint32_t aSkipStart)
{
Accessible* parent = mTextLeaf->Parent();
if (!parent)
return;
mHyperText = parent->AsHyperText();
if (!mHyperText) {
NS_ERROR("Text leaf parent is not hypertext!");
return;
}
// Get the text leaf accessible offset and invalidate cached offsets after it.
mTextOffset = mHyperText->GetChildOffset(mTextLeaf, true);
NS_ASSERTION(mTextOffset != -1,
"Text leaf hasn't offset within hyper text!");
uint32_t oldLen = aOldText.Length(), newLen = aNewText.Length();
uint32_t minLen = std::min(oldLen, newLen);
// Trim coinciding substrings from the end.
uint32_t skipEnd = 0;
while (minLen - skipEnd > aSkipStart &&
aNewText[newLen - skipEnd - 1] == aOldText[oldLen - skipEnd - 1]) {
skipEnd++;
}
uint32_t strLen1 = oldLen - aSkipStart - skipEnd;
uint32_t strLen2 = newLen - aSkipStart - skipEnd;
const nsAString& str1 = Substring(aOldText, aSkipStart, strLen1);
const nsAString& str2 = Substring(aNewText, aSkipStart, strLen2);
// Increase offset of the text leaf on skipped characters amount.
mTextOffset += aSkipStart;
// It could be single insertion or removal or the case of long strings. Do not
// calculate the difference between long strings and prefer to fire pair of
// insert/remove events as the old string was replaced on the new one.
if (strLen1 == 0 || strLen2 == 0 ||
strLen1 > kMaxStrLen || strLen2 > kMaxStrLen) {
if (strLen1 > 0) {
// Fire text change event for removal.
RefPtr<AccEvent> textRemoveEvent =
new AccTextChangeEvent(mHyperText, mTextOffset, str1, false);
mDocument->FireDelayedEvent(textRemoveEvent);
}
if (strLen2 > 0) {
// Fire text change event for insertion.
RefPtr<AccEvent> textInsertEvent =
new AccTextChangeEvent(mHyperText, mTextOffset, str2, true);
mDocument->FireDelayedEvent(textInsertEvent);
}
mDocument->MaybeNotifyOfValueChange(mHyperText);
// Update the text.
mTextLeaf->SetText(aNewText);
return;
}
// Otherwise find the difference between strings and fire events.
// Note: we can skip initial and final coinciding characters since they don't
// affect the Levenshtein distance.
// Compute the flat structured matrix need to compute the difference.
uint32_t len1 = strLen1 + 1, len2 = strLen2 + 1;
uint32_t* entries = new uint32_t[len1 * len2];
for (uint32_t colIdx = 0; colIdx < len1; colIdx++)
entries[colIdx] = colIdx;
uint32_t* row = entries;
for (uint32_t rowIdx = 1; rowIdx < len2; rowIdx++) {
uint32_t* prevRow = row;
row += len1;
row[0] = rowIdx;
for (uint32_t colIdx = 1; colIdx < len1; colIdx++) {
if (str1[colIdx - 1] != str2[rowIdx - 1]) {
uint32_t left = row[colIdx - 1];
uint32_t up = prevRow[colIdx];
uint32_t upleft = prevRow[colIdx - 1];
row[colIdx] = std::min(upleft, std::min(left, up)) + 1;
} else {
row[colIdx] = prevRow[colIdx - 1];
}
}
}
// Compute events based on the difference.
nsTArray<RefPtr<AccEvent> > events;
ComputeTextChangeEvents(str1, str2, entries, events);
delete [] entries;
// Fire events.
for (int32_t idx = events.Length() - 1; idx >= 0; idx--)
mDocument->FireDelayedEvent(events[idx]);
mDocument->MaybeNotifyOfValueChange(mHyperText);
// Update the text.
mTextLeaf->SetText(aNewText);
}
void
TextUpdater::ComputeTextChangeEvents(const nsAString& aStr1,
const nsAString& aStr2,
uint32_t* aEntries,
nsTArray<RefPtr<AccEvent> >& aEvents)
{
int32_t colIdx = aStr1.Length(), rowIdx = aStr2.Length();
// Point at which strings last matched.
int32_t colEnd = colIdx;
int32_t rowEnd = rowIdx;
int32_t colLen = colEnd + 1;
uint32_t* row = aEntries + rowIdx * colLen;
uint32_t dist = row[colIdx]; // current Levenshtein distance
while (rowIdx && colIdx) { // stop when we can't move diagonally
if (aStr1[colIdx - 1] == aStr2[rowIdx - 1]) { // match
if (rowIdx < rowEnd) { // deal with any pending insertion
FireInsertEvent(Substring(aStr2, rowIdx, rowEnd - rowIdx),
rowIdx, aEvents);
}
if (colIdx < colEnd) { // deal with any pending deletion
FireDeleteEvent(Substring(aStr1, colIdx, colEnd - colIdx),
rowIdx, aEvents);
}
colEnd = --colIdx; // reset the match point
rowEnd = --rowIdx;
row -= colLen;
continue;
}
--dist;
if (dist == row[colIdx - 1 - colLen]) { // substitution
--colIdx;
--rowIdx;
row -= colLen;
continue;
}
if (dist == row[colIdx - colLen]) { // insertion
--rowIdx;
row -= colLen;
continue;
}
if (dist == row[colIdx - 1]) { // deletion
--colIdx;
continue;
}
NS_NOTREACHED("huh?");
return;
}
if (rowEnd)
FireInsertEvent(Substring(aStr2, 0, rowEnd), 0, aEvents);
if (colEnd)
FireDeleteEvent(Substring(aStr1, 0, colEnd), 0, aEvents);
}

View file

@ -0,0 +1,96 @@
/* -*- 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/. */
#ifndef mozilla_a11y_TextUpdater_h__
#define mozilla_a11y_TextUpdater_h__
#include "AccEvent.h"
#include "HyperTextAccessible.h"
namespace mozilla {
namespace a11y {
/**
* Used to find a difference between old and new text and fire text change
* events.
*/
class TextUpdater
{
public:
/**
* Start text of the text leaf update.
*/
static void Run(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf,
const nsAString& aNewText);
private:
TextUpdater(DocAccessible* aDocument, TextLeafAccessible* aTextLeaf) :
mDocument(aDocument), mTextLeaf(aTextLeaf), mHyperText(nullptr),
mTextOffset(-1) { }
~TextUpdater()
{ mDocument = nullptr; mTextLeaf = nullptr; mHyperText = nullptr; }
/**
* Update text of the text leaf accessible, fire text change and value change
* (if applicable) events for its container hypertext accessible.
*/
void DoUpdate(const nsAString& aNewText, const nsAString& aOldText,
uint32_t aSkipStart);
private:
TextUpdater();
TextUpdater(const TextUpdater&);
TextUpdater& operator=(const TextUpdater&);
/**
* Fire text change events based on difference between strings.
*/
void ComputeTextChangeEvents(const nsAString& aStr1,
const nsAString& aStr2,
uint32_t* aEntries,
nsTArray<RefPtr<AccEvent> >& aEvents);
/**
* Helper to create text change events for inserted text.
*/
inline void FireInsertEvent(const nsAString& aText, uint32_t aAddlOffset,
nsTArray<RefPtr<AccEvent> >& aEvents)
{
RefPtr<AccEvent> event =
new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset,
aText, true);
aEvents.AppendElement(event);
}
/**
* Helper to create text change events for removed text.
*/
inline void FireDeleteEvent(const nsAString& aText, uint32_t aAddlOffset,
nsTArray<RefPtr<AccEvent> >& aEvents)
{
RefPtr<AccEvent> event =
new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset,
aText, false);
aEvents.AppendElement(event);
}
/**
* The constant used to skip string difference calculation in case of long
* strings.
*/
const static uint32_t kMaxStrLen = 1 << 6;
private:
DocAccessible* mDocument;
TextLeafAccessible* mTextLeaf;
HyperTextAccessible* mHyperText;
int32_t mTextOffset;
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,325 @@
/* -*- 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 "TreeWalker.h"
#include "Accessible.h"
#include "AccIterator.h"
#include "nsAccessibilityService.h"
#include "DocAccessible.h"
#include "mozilla/dom/ChildIterator.h"
#include "mozilla/dom/Element.h"
using namespace mozilla;
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// TreeWalker
////////////////////////////////////////////////////////////////////////////////
TreeWalker::
TreeWalker(Accessible* aContext) :
mDoc(aContext->Document()), mContext(aContext), mAnchorNode(nullptr),
mARIAOwnsIdx(0),
mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(0),
mPhase(eAtStart)
{
mChildFilter |= mContext->NoXBLKids() ?
nsIContent::eAllButXBL : nsIContent::eAllChildren;
mAnchorNode = mContext->IsDoc() ?
mDoc->DocumentNode()->GetRootElement() : mContext->GetContent();
MOZ_COUNT_CTOR(TreeWalker);
}
TreeWalker::
TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags) :
mDoc(aContext->Document()), mContext(aContext), mAnchorNode(aAnchorNode),
mARIAOwnsIdx(0),
mChildFilter(nsIContent::eSkipPlaceholderContent), mFlags(aFlags),
mPhase(eAtStart)
{
MOZ_ASSERT(mFlags & eWalkCache, "This constructor cannot be used for tree creation");
MOZ_ASSERT(aAnchorNode, "No anchor node for the accessible tree walker");
mChildFilter |= mContext->NoXBLKids() ?
nsIContent::eAllButXBL : nsIContent::eAllChildren;
MOZ_COUNT_CTOR(TreeWalker);
}
TreeWalker::~TreeWalker()
{
MOZ_COUNT_DTOR(TreeWalker);
}
Accessible*
TreeWalker::Scope(nsIContent* aAnchorNode)
{
Reset();
mAnchorNode = aAnchorNode;
bool skipSubtree = false;
Accessible* acc = AccessibleFor(aAnchorNode, 0, &skipSubtree);
if (acc) {
mPhase = eAtEnd;
return acc;
}
return skipSubtree ? nullptr : Next();
}
bool
TreeWalker::Seek(nsIContent* aChildNode)
{
MOZ_ASSERT(aChildNode, "Child cannot be null");
Reset();
if (mAnchorNode == aChildNode) {
return true;
}
nsIContent* childNode = nullptr;
nsINode* parentNode = aChildNode;
do {
childNode = parentNode->AsContent();
parentNode = childNode->HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) &&
(mChildFilter & nsIContent::eAllButXBL) ?
childNode->GetParentNode() : childNode->GetFlattenedTreeParent();
if (!parentNode || !parentNode->IsElement()) {
return false;
}
// If ARIA owned child.
Accessible* child = mDoc->GetAccessible(childNode);
if (child && child->IsRelocated()) {
if (child->Parent() != mContext) {
return false;
}
Accessible* ownedChild = nullptr;
while ((ownedChild = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx++)) &&
ownedChild != child);
MOZ_ASSERT(ownedChild, "A child has to be in ARIA owned elements");
mPhase = eAtARIAOwns;
return true;
}
// Look in DOM.
dom::AllChildrenIterator* iter = PrependState(parentNode->AsElement(), true);
if (!iter->Seek(childNode)) {
return false;
}
if (parentNode == mAnchorNode) {
mPhase = eAtDOM;
return true;
}
} while (true);
return false;
}
Accessible*
TreeWalker::Next()
{
if (mStateStack.IsEmpty()) {
if (mPhase == eAtEnd) {
return nullptr;
}
if (mPhase == eAtDOM || mPhase == eAtARIAOwns) {
mPhase = eAtARIAOwns;
Accessible* child = mDoc->ARIAOwnedAt(mContext, mARIAOwnsIdx);
if (child) {
mARIAOwnsIdx++;
return child;
}
mPhase = eAtEnd;
return nullptr;
}
if (!mAnchorNode) {
mPhase = eAtEnd;
return nullptr;
}
mPhase = eAtDOM;
PushState(mAnchorNode, true);
}
dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
while (top) {
while (nsIContent* childNode = top->GetNextChild()) {
bool skipSubtree = false;
Accessible* child = AccessibleFor(childNode, mFlags, &skipSubtree);
if (child) {
return child;
}
// Walk down the subtree if allowed.
if (!skipSubtree && childNode->IsElement()) {
top = PushState(childNode, true);
}
}
top = PopState();
}
// If we traversed the whole subtree of the anchor node. Move to next node
// relative anchor node within the context subtree if asked.
if (mFlags != eWalkContextTree) {
// eWalkCache flag presence indicates that the search is scoped to the
// anchor (no ARIA owns stuff).
if (mFlags & eWalkCache) {
mPhase = eAtEnd;
return nullptr;
}
return Next();
}
nsINode* contextNode = mContext->GetNode();
while (mAnchorNode != contextNode) {
nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
if (!parentNode || !parentNode->IsElement())
return nullptr;
nsIContent* parent = parentNode->AsElement();
top = PushState(parent, true);
if (top->Seek(mAnchorNode)) {
mAnchorNode = parent;
return Next();
}
// XXX We really should never get here, it means we're trying to find an
// accessible for a dom node where iterating over its parent's children
// doesn't return it. However this sometimes happens when we're asked for
// the nearest accessible to place holder content which we ignore.
mAnchorNode = parent;
}
return Next();
}
Accessible*
TreeWalker::Prev()
{
if (mStateStack.IsEmpty()) {
if (mPhase == eAtStart || mPhase == eAtDOM) {
mPhase = eAtStart;
return nullptr;
}
if (mPhase == eAtEnd) {
mARIAOwnsIdx = mDoc->ARIAOwnedCount(mContext);
mPhase = eAtARIAOwns;
}
if (mPhase == eAtARIAOwns) {
if (mARIAOwnsIdx > 0) {
return mDoc->ARIAOwnedAt(mContext, --mARIAOwnsIdx);
}
if (!mAnchorNode) {
mPhase = eAtStart;
return nullptr;
}
mPhase = eAtDOM;
PushState(mAnchorNode, false);
}
}
dom::AllChildrenIterator* top = &mStateStack[mStateStack.Length() - 1];
while (top) {
while (nsIContent* childNode = top->GetPreviousChild()) {
// No accessible creation on the way back.
bool skipSubtree = false;
Accessible* child = AccessibleFor(childNode, eWalkCache, &skipSubtree);
if (child) {
return child;
}
// Walk down into subtree to find accessibles.
if (!skipSubtree && childNode->IsElement()) {
top = PushState(childNode, false);
}
}
top = PopState();
}
// Move to a previous node relative the anchor node within the context
// subtree if asked.
if (mFlags != eWalkContextTree) {
mPhase = eAtStart;
return nullptr;
}
nsINode* contextNode = mContext->GetNode();
while (mAnchorNode != contextNode) {
nsINode* parentNode = mAnchorNode->GetFlattenedTreeParent();
if (!parentNode || !parentNode->IsElement()) {
return nullptr;
}
nsIContent* parent = parentNode->AsElement();
top = PushState(parent, true);
if (top->Seek(mAnchorNode)) {
mAnchorNode = parent;
return Prev();
}
mAnchorNode = parent;
}
mPhase = eAtStart;
return nullptr;
}
Accessible*
TreeWalker::AccessibleFor(nsIContent* aNode, uint32_t aFlags, bool* aSkipSubtree)
{
// Ignore the accessible and its subtree if it was repositioned by means
// of aria-owns.
Accessible* child = mDoc->GetAccessible(aNode);
if (child) {
if (child->IsRelocated()) {
*aSkipSubtree = true;
return nullptr;
}
return child;
}
// Create an accessible if allowed.
if (!(aFlags & eWalkCache) && mContext->IsAcceptableChild(aNode)) {
// We may have ARIA owned element in the dependent attributes map, but the
// element may be not allowed for this ARIA owns relation, if the relation
// crosses out XBL anonymous content boundaries. In this case we won't
// create an accessible object for it, when aria-owns is processed, which
// may make the element subtree inaccessible. To avoid that let's create
// an accessible object now, and later, if allowed, move it in the tree,
// when aria-owns relation is processed.
if (mDoc->RelocateARIAOwnedIfNeeded(aNode) && !aNode->IsXULElement()) {
*aSkipSubtree = true;
return nullptr;
}
return GetAccService()->CreateAccessible(aNode, mContext, aSkipSubtree);
}
return nullptr;
}
dom::AllChildrenIterator*
TreeWalker::PopState()
{
size_t length = mStateStack.Length();
mStateStack.RemoveElementAt(length - 1);
return mStateStack.IsEmpty() ? nullptr : &mStateStack.LastElement();
}

View file

@ -0,0 +1,144 @@
/* -*- 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/. */
#ifndef mozilla_a11y_TreeWalker_h_
#define mozilla_a11y_TreeWalker_h_
#include "mozilla/Attributes.h"
#include <stdint.h>
#include "mozilla/dom/ChildIterator.h"
#include "nsCOMPtr.h"
class nsIContent;
namespace mozilla {
namespace a11y {
class Accessible;
class DocAccessible;
/**
* This class is used to walk the DOM tree to create accessible tree.
*/
class TreeWalker final
{
public:
enum {
// used to walk the existing tree of the given node
eWalkCache = 1,
// used to walk the context tree starting from given node
eWalkContextTree = 2 | eWalkCache
};
/**
* Used to navigate and create if needed the accessible children.
*/
explicit TreeWalker(Accessible* aContext);
/**
* Used to navigate the accessible children relative to the anchor.
*
* @param aContext [in] container accessible for the given node, used to
* define accessible context
* @param aAnchorNode [in] the node the search will be prepared relative to
* @param aFlags [in] flags (see enum above)
*/
TreeWalker(Accessible* aContext, nsIContent* aAnchorNode, uint32_t aFlags = eWalkCache);
~TreeWalker();
/**
* Resets the walker state, and sets the given node as an anchor. Returns a
* first accessible element within the node including the node itself.
*/
Accessible* Scope(nsIContent* aAnchorNode);
/**
* Resets the walker state.
*/
void Reset()
{
mPhase = eAtStart;
mStateStack.Clear();
mARIAOwnsIdx = 0;
}
/**
* Sets the walker state to the given child node if it's within the anchor.
*/
bool Seek(nsIContent* aChildNode);
/**
* Return the next/prev accessible.
*
* @note Returned accessible is bound to the document, if the accessible is
* rejected during tree creation then the caller should be unbind it
* from the document.
*/
Accessible* Next();
Accessible* Prev();
Accessible* Context() const { return mContext; }
DocAccessible* Document() const { return mDoc; }
private:
TreeWalker();
TreeWalker(const TreeWalker&);
TreeWalker& operator =(const TreeWalker&);
/**
* Return an accessible for the given node if any.
*/
Accessible* AccessibleFor(nsIContent* aNode, uint32_t aFlags,
bool* aSkipSubtree);
/**
* Create new state for the given node and push it on top of stack / at bottom
* of stack.
*
* @note State stack is used to navigate up/down the DOM subtree during
* accessible children search.
*/
dom::AllChildrenIterator* PushState(nsIContent* aContent,
bool aStartAtBeginning)
{
return mStateStack.AppendElement(
dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
}
dom::AllChildrenIterator* PrependState(nsIContent* aContent,
bool aStartAtBeginning)
{
return mStateStack.InsertElementAt(0,
dom::AllChildrenIterator(aContent, mChildFilter, aStartAtBeginning));
}
/**
* Pop state from stack.
*/
dom::AllChildrenIterator* PopState();
DocAccessible* mDoc;
Accessible* mContext;
nsIContent* mAnchorNode;
AutoTArray<dom::AllChildrenIterator, 20> mStateStack;
uint32_t mARIAOwnsIdx;
int32_t mChildFilter;
uint32_t mFlags;
enum Phase {
eAtStart,
eAtDOM,
eAtARIAOwns,
eAtEnd
};
Phase mPhase;
};
} // namespace a11y
} // namespace mozilla
#endif // mozilla_a11y_TreeWalker_h_

114
accessible/base/moz.build Normal file
View file

@ -0,0 +1,114 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS += [
'AccEvent.h',
'nsAccessibilityService.h'
]
EXPORTS.mozilla.a11y += [
'AccTypes.h',
'DocManager.h',
'FocusManager.h',
'Platform.h',
'RelationType.h',
'Role.h',
'SelectionManager.h',
'States.h',
]
if CONFIG['MOZ_DEBUG']:
EXPORTS.mozilla.a11y += [
'Logging.h',
]
UNIFIED_SOURCES += [
'AccessibleOrProxy.cpp',
'AccEvent.cpp',
'AccGroupInfo.cpp',
'AccIterator.cpp',
'ARIAMap.cpp',
'ARIAStateMap.cpp',
'Asserts.cpp',
'DocManager.cpp',
'EmbeddedObjCollector.cpp',
'EventQueue.cpp',
'EventTree.cpp',
'Filters.cpp',
'FocusManager.cpp',
'NotificationController.cpp',
'nsAccessibilityService.cpp',
'nsAccessiblePivot.cpp',
'nsAccUtils.cpp',
'nsCoreUtils.cpp',
'nsEventShell.cpp',
'nsTextEquivUtils.cpp',
'SelectionManager.cpp',
'StyleInfo.cpp',
'TextAttrs.cpp',
'TextRange.cpp',
'TextUpdater.cpp',
'TreeWalker.cpp',
]
if CONFIG['A11Y_LOG']:
UNIFIED_SOURCES += [
'Logging.cpp',
]
LOCAL_INCLUDES += [
'/accessible/generic',
'/accessible/html',
'/accessible/ipc',
]
if CONFIG['OS_ARCH'] == 'WINNT':
LOCAL_INCLUDES += [
'/accessible/ipc/win',
]
else:
LOCAL_INCLUDES += [
'/accessible/ipc/other',
]
LOCAL_INCLUDES += [
'/accessible/xpcom',
'/accessible/xul',
'/dom/base',
'/dom/xbl',
'/ipc/chromium/src',
'/layout/generic',
'/layout/style',
'/layout/svg',
'/layout/xul',
'/layout/xul/tree/',
]
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
LOCAL_INCLUDES += [
'/accessible/atk',
]
CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
LOCAL_INCLUDES += [
'/accessible/windows/ia2',
'/accessible/windows/msaa',
]
elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
LOCAL_INCLUDES += [
'/accessible/mac',
]
else:
LOCAL_INCLUDES += [
'/accessible/other',
]
FINAL_LIBRARY = 'xul'
include('/ipc/chromium/chromium-config.mozbuild')
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']

View file

@ -0,0 +1,28 @@
/* -*- 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/. */
#ifndef _nsAccCache_H_
#define _nsAccCache_H_
#include "xpcAccessibleDocument.h"
////////////////////////////////////////////////////////////////////////////////
// Accessible cache utils
////////////////////////////////////////////////////////////////////////////////
template <class T>
void
UnbindCacheEntriesFromDocument(
nsRefPtrHashtable<nsPtrHashKey<const void>, T>& aCache)
{
for (auto iter = aCache.Iter(); !iter.Done(); iter.Next()) {
T* accessible = iter.Data();
MOZ_ASSERT(accessible && !accessible->IsDefunct());
accessible->Document()->UnbindFromDocument(accessible);
iter.Remove();
}
}
#endif

View file

@ -0,0 +1,447 @@
/* -*- 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 "nsAccUtils.h"
#include "Accessible-inl.h"
#include "ARIAMap.h"
#include "nsAccessibilityService.h"
#include "nsCoreUtils.h"
#include "DocAccessible.h"
#include "HyperTextAccessible.h"
#include "nsIAccessibleTypes.h"
#include "Role.h"
#include "States.h"
#include "TextLeafAccessible.h"
#include "nsIDOMXULContainerElement.h"
#include "nsIPersistentProperties2.h"
#include "mozilla/dom/Element.h"
using namespace mozilla;
using namespace mozilla::a11y;
void
nsAccUtils::GetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom *aAttrName, nsAString& aAttrValue)
{
aAttrValue.Truncate();
aAttributes->GetStringProperty(nsAtomCString(aAttrName), aAttrValue);
}
void
nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom *aAttrName, const nsAString& aAttrValue)
{
nsAutoString oldValue;
aAttributes->SetStringProperty(nsAtomCString(aAttrName), aAttrValue, oldValue);
}
void
nsAccUtils::SetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom* aAttrName, nsIAtom* aAttrValue)
{
nsAutoString oldValue;
aAttributes->SetStringProperty(nsAtomCString(aAttrName),
nsAtomString(aAttrValue), oldValue);
}
void
nsAccUtils::SetAccGroupAttrs(nsIPersistentProperties *aAttributes,
int32_t aLevel, int32_t aSetSize,
int32_t aPosInSet)
{
nsAutoString value;
if (aLevel) {
value.AppendInt(aLevel);
SetAccAttr(aAttributes, nsGkAtoms::level, value);
}
if (aSetSize && aPosInSet) {
value.Truncate();
value.AppendInt(aPosInSet);
SetAccAttr(aAttributes, nsGkAtoms::posinset, value);
value.Truncate();
value.AppendInt(aSetSize);
SetAccAttr(aAttributes, nsGkAtoms::setsize, value);
}
}
int32_t
nsAccUtils::GetDefaultLevel(Accessible* aAccessible)
{
roles::Role role = aAccessible->Role();
if (role == roles::OUTLINEITEM)
return 1;
if (role == roles::ROW) {
Accessible* parent = aAccessible->Parent();
// It is a row inside flatten treegrid. Group level is always 1 until it
// is overriden by aria-level attribute.
if (parent && parent->Role() == roles::TREE_TABLE)
return 1;
}
return 0;
}
int32_t
nsAccUtils::GetARIAOrDefaultLevel(Accessible* aAccessible)
{
int32_t level = 0;
nsCoreUtils::GetUIntAttr(aAccessible->GetContent(),
nsGkAtoms::aria_level, &level);
if (level != 0)
return level;
return GetDefaultLevel(aAccessible);
}
int32_t
nsAccUtils::GetLevelForXULContainerItem(nsIContent *aContent)
{
nsCOMPtr<nsIDOMXULContainerItemElement> item(do_QueryInterface(aContent));
if (!item)
return 0;
nsCOMPtr<nsIDOMXULContainerElement> container;
item->GetParentContainer(getter_AddRefs(container));
if (!container)
return 0;
// Get level of the item.
int32_t level = -1;
while (container) {
level++;
nsCOMPtr<nsIDOMXULContainerElement> parentContainer;
container->GetParentContainer(getter_AddRefs(parentContainer));
parentContainer.swap(container);
}
return level;
}
void
nsAccUtils::SetLiveContainerAttributes(nsIPersistentProperties *aAttributes,
nsIContent* aStartContent,
dom::Element* aTopEl)
{
nsAutoString live, relevant, busy;
nsIContent* ancestor = aStartContent;
while (ancestor) {
// container-relevant attribute
if (relevant.IsEmpty() &&
HasDefinedARIAToken(ancestor, nsGkAtoms::aria_relevant) &&
ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_relevant, relevant))
SetAccAttr(aAttributes, nsGkAtoms::containerRelevant, relevant);
// container-live, and container-live-role attributes
if (live.IsEmpty()) {
const nsRoleMapEntry* role = nullptr;
if (ancestor->IsElement()) {
role = aria::GetRoleMap(ancestor->AsElement());
}
if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live, live);
} else if (role) {
GetLiveAttrValue(role->liveAttRule, live);
}
if (!live.IsEmpty()) {
SetAccAttr(aAttributes, nsGkAtoms::containerLive, live);
if (role) {
SetAccAttr(aAttributes, nsGkAtoms::containerLiveRole,
role->ARIARoleString());
}
}
}
// container-atomic attribute
if (ancestor->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_atomic,
nsGkAtoms::_true, eCaseMatters)) {
SetAccAttr(aAttributes, nsGkAtoms::containerAtomic,
NS_LITERAL_STRING("true"));
}
// container-busy attribute
if (busy.IsEmpty() &&
HasDefinedARIAToken(ancestor, nsGkAtoms::aria_busy) &&
ancestor->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_busy, busy))
SetAccAttr(aAttributes, nsGkAtoms::containerBusy, busy);
if (ancestor == aTopEl)
break;
ancestor = ancestor->GetParent();
if (!ancestor)
ancestor = aTopEl; // Use <body>/<frameset>
}
}
bool
nsAccUtils::HasDefinedARIAToken(nsIContent *aContent, nsIAtom *aAtom)
{
NS_ASSERTION(aContent, "aContent is null in call to HasDefinedARIAToken!");
if (!aContent->HasAttr(kNameSpaceID_None, aAtom) ||
aContent->AttrValueIs(kNameSpaceID_None, aAtom,
nsGkAtoms::_empty, eCaseMatters) ||
aContent->AttrValueIs(kNameSpaceID_None, aAtom,
nsGkAtoms::_undefined, eCaseMatters)) {
return false;
}
return true;
}
nsIAtom*
nsAccUtils::GetARIAToken(dom::Element* aElement, nsIAtom* aAttr)
{
if (!HasDefinedARIAToken(aElement, aAttr))
return nsGkAtoms::_empty;
static nsIContent::AttrValuesArray tokens[] =
{ &nsGkAtoms::_false, &nsGkAtoms::_true,
&nsGkAtoms::mixed, nullptr};
int32_t idx = aElement->FindAttrValueIn(kNameSpaceID_None,
aAttr, tokens, eCaseMatters);
if (idx >= 0)
return *(tokens[idx]);
return nullptr;
}
Accessible*
nsAccUtils::GetSelectableContainer(Accessible* aAccessible, uint64_t aState)
{
if (!aAccessible)
return nullptr;
if (!(aState & states::SELECTABLE))
return nullptr;
Accessible* parent = aAccessible;
while ((parent = parent->Parent()) && !parent->IsSelect()) {
if (parent->Role() == roles::PANE)
return nullptr;
}
return parent;
}
bool
nsAccUtils::IsARIASelected(Accessible* aAccessible)
{
return aAccessible->GetContent()->
AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_selected,
nsGkAtoms::_true, eCaseMatters);
}
Accessible*
nsAccUtils::TableFor(Accessible* aRow)
{
if (aRow) {
Accessible* table = aRow->Parent();
if (table) {
roles::Role tableRole = table->Role();
if (tableRole == roles::GROUPING) { // if there's a rowgroup.
table = table->Parent();
if (table)
tableRole = table->Role();
}
return (tableRole == roles::TABLE || tableRole == roles::TREE_TABLE ||
tableRole == roles::MATHML_TABLE) ? table : nullptr;
}
}
return nullptr;
}
HyperTextAccessible*
nsAccUtils::GetTextContainer(nsINode* aNode)
{
// Get text accessible containing the result node.
DocAccessible* doc =
GetAccService()->GetDocAccessible(aNode->OwnerDoc());
Accessible* accessible =
doc ? doc->GetAccessibleOrContainer(aNode) : nullptr;
if (!accessible)
return nullptr;
do {
HyperTextAccessible* textAcc = accessible->AsHyperText();
if (textAcc)
return textAcc;
accessible = accessible->Parent();
} while (accessible);
return nullptr;
}
nsIntPoint
nsAccUtils::ConvertToScreenCoords(int32_t aX, int32_t aY,
uint32_t aCoordinateType,
Accessible* aAccessible)
{
nsIntPoint coords(aX, aY);
switch (aCoordinateType) {
case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
break;
case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE:
{
coords += nsCoreUtils::GetScreenCoordsForWindow(aAccessible->GetNode());
break;
}
case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE:
{
coords += GetScreenCoordsForParent(aAccessible);
break;
}
default:
NS_NOTREACHED("invalid coord type!");
}
return coords;
}
void
nsAccUtils::ConvertScreenCoordsTo(int32_t *aX, int32_t *aY,
uint32_t aCoordinateType,
Accessible* aAccessible)
{
switch (aCoordinateType) {
case nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE:
break;
case nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE:
{
nsIntPoint coords = nsCoreUtils::GetScreenCoordsForWindow(aAccessible->GetNode());
*aX -= coords.x;
*aY -= coords.y;
break;
}
case nsIAccessibleCoordinateType::COORDTYPE_PARENT_RELATIVE:
{
nsIntPoint coords = GetScreenCoordsForParent(aAccessible);
*aX -= coords.x;
*aY -= coords.y;
break;
}
default:
NS_NOTREACHED("invalid coord type!");
}
}
nsIntPoint
nsAccUtils::GetScreenCoordsForParent(Accessible* aAccessible)
{
Accessible* parent = aAccessible->Parent();
if (!parent)
return nsIntPoint(0, 0);
nsIFrame *parentFrame = parent->GetFrame();
if (!parentFrame)
return nsIntPoint(0, 0);
nsRect rect = parentFrame->GetScreenRectInAppUnits();
return nsPoint(rect.x, rect.y).
ToNearestPixels(parentFrame->PresContext()->AppUnitsPerDevPixel());
}
bool
nsAccUtils::GetLiveAttrValue(uint32_t aRule, nsAString& aValue)
{
switch (aRule) {
case eOffLiveAttr:
aValue = NS_LITERAL_STRING("off");
return true;
case ePoliteLiveAttr:
aValue = NS_LITERAL_STRING("polite");
return true;
}
return false;
}
#ifdef DEBUG
bool
nsAccUtils::IsTextInterfaceSupportCorrect(Accessible* aAccessible)
{
// Don't test for accessible docs, it makes us create accessibles too
// early and fire mutation events before we need to
if (aAccessible->IsDoc())
return true;
bool foundText = false;
uint32_t childCount = aAccessible->ChildCount();
for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
Accessible* child = aAccessible->GetChildAt(childIdx);
if (child->IsText()) {
foundText = true;
break;
}
}
return !foundText || aAccessible->IsHyperText();
}
#endif
uint32_t
nsAccUtils::TextLength(Accessible* aAccessible)
{
if (!aAccessible->IsText()) {
return 1;
}
TextLeafAccessible* textLeaf = aAccessible->AsTextLeaf();
if (textLeaf)
return textLeaf->Text().Length();
// For list bullets (or anything other accessible which would compute its own
// text. They don't have their own frame.
// XXX In the future, list bullets may have frame and anon content, so
// we should be able to remove this at that point
nsAutoString text;
aAccessible->AppendTextTo(text); // Get all the text
return text.Length();
}
bool
nsAccUtils::MustPrune(Accessible* aAccessible)
{
roles::Role role = aAccessible->Role();
// Don't prune the tree for certain roles if the tree is more complex than
// a single text leaf.
return
(role == roles::MENUITEM ||
role == roles::COMBOBOX_OPTION ||
role == roles::OPTION ||
role == roles::ENTRY ||
role == roles::FLAT_EQUATION ||
role == roles::PASSWORD_TEXT ||
role == roles::PUSHBUTTON ||
role == roles::TOGGLE_BUTTON ||
role == roles::GRAPHIC ||
role == roles::SLIDER ||
role == roles::PROGRESSBAR ||
role == roles::SEPARATOR) &&
aAccessible->ContentChildCount() == 1 &&
aAccessible->ContentChildAt(0)->IsTextLeaf();
}

View file

@ -0,0 +1,243 @@
/* -*- 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/. */
#ifndef nsAccUtils_h_
#define nsAccUtils_h_
#include "mozilla/a11y/Accessible.h"
#include "nsAccessibilityService.h"
#include "nsCoreUtils.h"
#include "nsIDocShell.h"
#include "nsPoint.h"
namespace mozilla {
namespace dom {
class Element;
}
namespace a11y {
class HyperTextAccessible;
class DocAccessible;
class nsAccUtils
{
public:
/**
* Returns value of attribute from the given attributes container.
*
* @param aAttributes - attributes container
* @param aAttrName - the name of requested attribute
* @param aAttrValue - value of attribute
*/
static void GetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom *aAttrName,
nsAString& aAttrValue);
/**
* Set value of attribute for the given attributes container.
*
* @param aAttributes - attributes container
* @param aAttrName - the name of requested attribute
* @param aAttrValue - new value of attribute
*/
static void SetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom *aAttrName,
const nsAString& aAttrValue);
static void SetAccAttr(nsIPersistentProperties *aAttributes,
nsIAtom* aAttrName,
nsIAtom* aAttrValue);
/**
* Set group attributes ('level', 'setsize', 'posinset').
*/
static void SetAccGroupAttrs(nsIPersistentProperties *aAttributes,
int32_t aLevel, int32_t aSetSize,
int32_t aPosInSet);
/**
* Get default value of the level for the given accessible.
*/
static int32_t GetDefaultLevel(Accessible* aAcc);
/**
* Return ARIA level value or the default one if ARIA is missed for the
* given accessible.
*/
static int32_t GetARIAOrDefaultLevel(Accessible* aAccessible);
/**
* Compute group level for nsIDOMXULContainerItemElement node.
*/
static int32_t GetLevelForXULContainerItem(nsIContent *aContent);
/**
* Set container-foo live region attributes for the given node.
*
* @param aAttributes where to store the attributes
* @param aStartContent node to start from
* @param aTopContent node to end at
*/
static void SetLiveContainerAttributes(nsIPersistentProperties *aAttributes,
nsIContent* aStartContent,
mozilla::dom::Element* aTopEl);
/**
* Any ARIA property of type boolean or NMTOKEN is undefined if the ARIA
* property is not present, or is "" or "undefined". Do not call
* this method for properties of type string, decimal, IDREF or IDREFS.
*
* Return true if the ARIA property is defined, otherwise false
*/
static bool HasDefinedARIAToken(nsIContent *aContent, nsIAtom *aAtom);
/**
* Return atomic value of ARIA attribute of boolean or NMTOKEN type.
*/
static nsIAtom* GetARIAToken(mozilla::dom::Element* aElement, nsIAtom* aAttr);
/**
* Return document accessible for the given DOM node.
*/
static DocAccessible* GetDocAccessibleFor(nsINode* aNode)
{
nsIPresShell *presShell = nsCoreUtils::GetPresShellFor(aNode);
return GetAccService()->GetDocAccessible(presShell);
}
/**
* Return document accessible for the given docshell.
*/
static DocAccessible* GetDocAccessibleFor(nsIDocShellTreeItem* aContainer)
{
nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aContainer));
return GetAccService()->GetDocAccessible(docShell->GetPresShell());
}
/**
* Return single or multi selectable container for the given item.
*
* @param aAccessible [in] the item accessible
* @param aState [in] the state of the item accessible
*/
static Accessible* GetSelectableContainer(Accessible* aAccessible,
uint64_t aState);
/**
* Return a text container accessible for the given node.
*/
static HyperTextAccessible* GetTextContainer(nsINode* aNode);
static Accessible* TableFor(Accessible* aRow);
/**
* Return true if the DOM node of given accessible has aria-selected="true"
* attribute.
*/
static bool IsARIASelected(Accessible* aAccessible);
/**
* Converts the given coordinates to coordinates relative screen.
*
* @param aX [in] the given x coord
* @param aY [in] the given y coord
* @param aCoordinateType [in] specifies coordinates origin (refer to
* nsIAccessibleCoordinateType)
* @param aAccessible [in] the accessible if coordinates are given
* relative it.
* @return converted coordinates
*/
static nsIntPoint ConvertToScreenCoords(int32_t aX, int32_t aY,
uint32_t aCoordinateType,
Accessible* aAccessible);
/**
* Converts the given coordinates relative screen to another coordinate
* system.
*
* @param aX [in, out] the given x coord
* @param aY [in, out] the given y coord
* @param aCoordinateType [in] specifies coordinates origin (refer to
* nsIAccessibleCoordinateType)
* @param aAccessible [in] the accessible if coordinates are given
* relative it
*/
static void ConvertScreenCoordsTo(int32_t* aX, int32_t* aY,
uint32_t aCoordinateType,
Accessible* aAccessible);
/**
* Returns coordinates relative screen for the parent of the given accessible.
*
* @param [in] aAccessible the accessible
*/
static nsIntPoint GetScreenCoordsForParent(Accessible* aAccessible);
/**
* Get the 'live' or 'container-live' object attribute value from the given
* ELiveAttrRule constant.
*
* @param aRule [in] rule constant (see ELiveAttrRule in nsAccMap.h)
* @param aValue [out] object attribute value
*
* @return true if object attribute should be exposed
*/
static bool GetLiveAttrValue(uint32_t aRule, nsAString& aValue);
#ifdef DEBUG
/**
* Detect whether the given accessible object implements nsIAccessibleText,
* when it is text or has text child node.
*/
static bool IsTextInterfaceSupportCorrect(Accessible* aAccessible);
#endif
/**
* Return text length of the given accessible, return 0 on failure.
*/
static uint32_t TextLength(Accessible* aAccessible);
/**
* Transform nsIAccessibleStates constants to internal state constant.
*/
static inline uint64_t To64State(uint32_t aState1, uint32_t aState2)
{
return static_cast<uint64_t>(aState1) +
(static_cast<uint64_t>(aState2) << 31);
}
/**
* Transform internal state constant to nsIAccessibleStates constants.
*/
static inline void To32States(uint64_t aState64,
uint32_t* aState1, uint32_t* aState2)
{
*aState1 = aState64 & 0x7fffffff;
if (aState2)
*aState2 = static_cast<uint32_t>(aState64 >> 31);
}
static uint32_t To32States(uint64_t aState, bool* aIsExtra)
{
uint32_t extraState = aState >> 31;
*aIsExtra = !!extraState;
return aState | extraState;
}
/**
* Return true if the given accessible can't have children. Used when exposing
* to platform accessibility APIs, should the children be pruned off?
*/
static bool MustPrune(Accessible* aAccessible);
};
} // namespace a11y
} // namespace mozilla
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,442 @@
/* -*- 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/. */
#ifndef __nsAccessibilityService_h__
#define __nsAccessibilityService_h__
#include "mozilla/a11y/DocManager.h"
#include "mozilla/a11y/FocusManager.h"
#include "mozilla/a11y/Role.h"
#include "mozilla/a11y/SelectionManager.h"
#include "mozilla/Preferences.h"
#include "nsIObserver.h"
#include "nsIAccessibleEvent.h"
#include "nsIEventListenerService.h"
#include "xpcAccessibilityService.h"
class nsImageFrame;
class nsIArray;
class nsIPersistentProperties;
class nsPluginFrame;
class nsITreeView;
namespace mozilla {
namespace a11y {
class ApplicationAccessible;
class xpcAccessibleApplication;
/**
* Return focus manager.
*/
FocusManager* FocusMgr();
/**
* Return selection manager.
*/
SelectionManager* SelectionMgr();
/**
* Returns the application accessible.
*/
ApplicationAccessible* ApplicationAcc();
xpcAccessibleApplication* XPCApplicationAcc();
typedef Accessible* (New_Accessible)(nsIContent* aContent, Accessible* aContext);
struct MarkupAttrInfo {
nsIAtom** name;
nsIAtom** value;
nsIAtom** DOMAttrName;
nsIAtom** DOMAttrValue;
};
struct MarkupMapInfo {
nsIAtom** tag;
New_Accessible* new_func;
a11y::role role;
MarkupAttrInfo attrs[4];
};
} // namespace a11y
} // namespace mozilla
class nsAccessibilityService final : public mozilla::a11y::DocManager,
public mozilla::a11y::FocusManager,
public mozilla::a11y::SelectionManager,
public nsIListenerChangeListener,
public nsIObserver
{
public:
typedef mozilla::a11y::Accessible Accessible;
typedef mozilla::a11y::DocAccessible DocAccessible;
// nsIListenerChangeListener
NS_IMETHOD ListenersChanged(nsIArray* aEventChanges) override;
protected:
~nsAccessibilityService();
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIOBSERVER
Accessible* GetRootDocumentAccessible(nsIPresShell* aPresShell,
bool aCanCreate);
already_AddRefed<Accessible>
CreatePluginAccessible(nsPluginFrame* aFrame, nsIContent* aContent,
Accessible* aContext);
/**
* Adds/remove ATK root accessible for gtk+ native window to/from children
* of the application accessible.
*/
Accessible* AddNativeRootAccessible(void* aAtkAccessible);
void RemoveNativeRootAccessible(Accessible* aRootAccessible);
bool HasAccessible(nsIDOMNode* aDOMNode);
/**
* Get a string equivalent for an accessilbe role value.
*/
void GetStringRole(uint32_t aRole, nsAString& aString);
/**
* Get a string equivalent for an accessible state/extra state.
*/
void GetStringStates(uint32_t aState, uint32_t aExtraState,
nsISupports **aStringStates);
/**
* Get a string equivalent for an accessible event value.
*/
void GetStringEventType(uint32_t aEventType, nsAString& aString);
/**
* Get a string equivalent for an accessible relation type.
*/
void GetStringRelationType(uint32_t aRelationType, nsAString& aString);
// nsAccesibilityService
/**
* Notification used to update the accessible tree when deck panel is
* switched.
*/
void DeckPanelSwitched(nsIPresShell* aPresShell, nsIContent* aDeckNode,
nsIFrame* aPrevBoxFrame, nsIFrame* aCurrentBoxFrame);
/**
* Notification used to update the accessible tree when new content is
* inserted.
*/
void ContentRangeInserted(nsIPresShell* aPresShell, nsIContent* aContainer,
nsIContent* aStartChild, nsIContent* aEndChild);
/**
* Notification used to update the accessible tree when content is removed.
*/
void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aChild);
void UpdateText(nsIPresShell* aPresShell, nsIContent* aContent);
/**
* Update XUL:tree accessible tree when treeview is changed.
*/
void TreeViewChanged(nsIPresShell* aPresShell, nsIContent* aContent,
nsITreeView* aView);
/**
* Notify of input@type="element" value change.
*/
void RangeValueChanged(nsIPresShell* aPresShell, nsIContent* aContent);
/**
* Update list bullet accessible.
*/
void UpdateListBullet(nsIPresShell* aPresShell,
nsIContent* aHTMLListItemContent,
bool aHasBullet);
/**
* Update the image map.
*/
void UpdateImageMap(nsImageFrame* aImageFrame);
/**
* Update the label accessible tree when rendered @value is changed.
*/
void UpdateLabelValue(nsIPresShell* aPresShell, nsIContent* aLabelElm,
const nsString& aNewValue);
/**
* Notify accessibility that anchor jump has been accomplished to the given
* target. Used by layout.
*/
void NotifyOfAnchorJumpTo(nsIContent *aTarget);
/**
* Notify that presshell is activated.
*/
void PresShellActivated(nsIPresShell* aPresShell);
/**
* Recreate an accessible for the given content node in the presshell.
*/
void RecreateAccessible(nsIPresShell* aPresShell, nsIContent* aContent);
void FireAccessibleEvent(uint32_t aEvent, Accessible* aTarget);
// nsAccessibiltiyService
/**
* Return true if accessibility service has been shutdown.
*/
static bool IsShutdown()
{
return gConsumers == 0;
};
/**
* Creates an accessible for the given DOM node.
*
* @param aNode [in] the given node
* @param aContext [in] context the accessible is created in
* @param aIsSubtreeHidden [out, optional] indicates whether the node's
* frame and its subtree is hidden
*/
Accessible* CreateAccessible(nsINode* aNode, Accessible* aContext,
bool* aIsSubtreeHidden = nullptr);
mozilla::a11y::role MarkupRole(const nsIContent* aContent) const
{
const mozilla::a11y::MarkupMapInfo* markupMap =
mMarkupMaps.Get(aContent->NodeInfo()->NameAtom());
return markupMap ? markupMap->role : mozilla::a11y::roles::NOTHING;
}
/**
* Set the object attribute defined by markup for the given element.
*/
void MarkupAttributes(const nsIContent* aContent,
nsIPersistentProperties* aAttributes) const;
/**
* A list of possible accessibility service consumers. Accessibility service
* can only be shut down when there are no remaining consumers.
*
* eXPCOM - accessibility service is used by XPCOM.
*
* eMainProcess - accessibility service was started by main process in the
* content process.
*
* ePlatformAPI - accessibility service is used by the platform api in the
* main process.
*/
enum ServiceConsumer
{
eXPCOM = 1 << 0,
eMainProcess = 1 << 1,
ePlatformAPI = 1 << 2,
};
private:
// nsAccessibilityService creation is controlled by friend
// GetOrCreateAccService, keep constructors private.
nsAccessibilityService();
nsAccessibilityService(const nsAccessibilityService&);
nsAccessibilityService& operator =(const nsAccessibilityService&);
private:
/**
* Initialize accessibility service.
*/
bool Init();
/**
* Shutdowns accessibility service.
*/
void Shutdown();
/**
* Create accessible for the element having XBL bindings.
*/
already_AddRefed<Accessible>
CreateAccessibleByType(nsIContent* aContent, DocAccessible* aDoc);
/**
* Create an accessible whose type depends on the given frame.
*/
already_AddRefed<Accessible>
CreateAccessibleByFrameType(nsIFrame* aFrame, nsIContent* aContent,
Accessible* aContext);
#ifdef MOZ_XUL
/**
* Create accessible for XUL tree element.
*/
already_AddRefed<Accessible>
CreateAccessibleForXULTree(nsIContent* aContent, DocAccessible* aDoc);
#endif
/**
* Reference for accessibility service instance.
*/
static nsAccessibilityService* gAccessibilityService;
/**
* Reference for application accessible instance.
*/
static mozilla::a11y::ApplicationAccessible* gApplicationAccessible;
static mozilla::a11y::xpcAccessibleApplication* gXPCApplicationAccessible;
/**
* Contains a set of accessibility service consumers.
*/
static uint32_t gConsumers;
nsDataHashtable<nsPtrHashKey<const nsIAtom>, const mozilla::a11y::MarkupMapInfo*> mMarkupMaps;
friend nsAccessibilityService* GetAccService();
friend nsAccessibilityService* GetOrCreateAccService(uint32_t);
friend void MaybeShutdownAccService(uint32_t);
friend mozilla::a11y::FocusManager* mozilla::a11y::FocusMgr();
friend mozilla::a11y::SelectionManager* mozilla::a11y::SelectionMgr();
friend mozilla::a11y::ApplicationAccessible* mozilla::a11y::ApplicationAcc();
friend mozilla::a11y::xpcAccessibleApplication* mozilla::a11y::XPCApplicationAcc();
friend class xpcAccessibilityService;
};
/**
* Return the accessibility service instance. (Handy global function)
*/
inline nsAccessibilityService*
GetAccService()
{
return nsAccessibilityService::gAccessibilityService;
}
/**
* Return accessibility service instance; creating one if necessary.
*/
nsAccessibilityService* GetOrCreateAccService(
uint32_t aNewConsumer = nsAccessibilityService::ePlatformAPI);
/**
* Shutdown accessibility service if needed.
*/
void MaybeShutdownAccService(uint32_t aFormerConsumer);
/**
* Return true if we're in a content process and not B2G.
*/
inline bool
IPCAccessibilityActive()
{
#ifdef MOZ_B2G
return false;
#else
return XRE_IsContentProcess() &&
mozilla::Preferences::GetBool("accessibility.ipc_architecture.enabled", true);
#endif
}
/**
* Map nsIAccessibleEvents constants to strings. Used by
* nsAccessibilityService::GetStringEventType() method.
*/
static const char kEventTypeNames[][40] = {
"unknown", //
"show", // EVENT_SHOW
"hide", // EVENT_HIDE
"reorder", // EVENT_REORDER
"active decendent change", // EVENT_ACTIVE_DECENDENT_CHANGED
"focus", // EVENT_FOCUS
"state change", // EVENT_STATE_CHANGE
"location change", // EVENT_LOCATION_CHANGE
"name changed", // EVENT_NAME_CHANGE
"description change", // EVENT_DESCRIPTION_CHANGE
"value change", // EVENT_VALUE_CHANGE
"help change", // EVENT_HELP_CHANGE
"default action change", // EVENT_DEFACTION_CHANGE
"action change", // EVENT_ACTION_CHANGE
"accelerator change", // EVENT_ACCELERATOR_CHANGE
"selection", // EVENT_SELECTION
"selection add", // EVENT_SELECTION_ADD
"selection remove", // EVENT_SELECTION_REMOVE
"selection within", // EVENT_SELECTION_WITHIN
"alert", // EVENT_ALERT
"foreground", // EVENT_FOREGROUND
"menu start", // EVENT_MENU_START
"menu end", // EVENT_MENU_END
"menupopup start", // EVENT_MENUPOPUP_START
"menupopup end", // EVENT_MENUPOPUP_END
"capture start", // EVENT_CAPTURE_START
"capture end", // EVENT_CAPTURE_END
"movesize start", // EVENT_MOVESIZE_START
"movesize end", // EVENT_MOVESIZE_END
"contexthelp start", // EVENT_CONTEXTHELP_START
"contexthelp end", // EVENT_CONTEXTHELP_END
"dragdrop start", // EVENT_DRAGDROP_START
"dragdrop end", // EVENT_DRAGDROP_END
"dialog start", // EVENT_DIALOG_START
"dialog end", // EVENT_DIALOG_END
"scrolling start", // EVENT_SCROLLING_START
"scrolling end", // EVENT_SCROLLING_END
"minimize start", // EVENT_MINIMIZE_START
"minimize end", // EVENT_MINIMIZE_END
"document load complete", // EVENT_DOCUMENT_LOAD_COMPLETE
"document reload", // EVENT_DOCUMENT_RELOAD
"document load stopped", // EVENT_DOCUMENT_LOAD_STOPPED
"document attributes changed", // EVENT_DOCUMENT_ATTRIBUTES_CHANGED
"document content changed", // EVENT_DOCUMENT_CONTENT_CHANGED
"property changed", // EVENT_PROPERTY_CHANGED
"page changed", // EVENT_PAGE_CHANGED
"text attribute changed", // EVENT_TEXT_ATTRIBUTE_CHANGED
"text caret moved", // EVENT_TEXT_CARET_MOVED
"text changed", // EVENT_TEXT_CHANGED
"text inserted", // EVENT_TEXT_INSERTED
"text removed", // EVENT_TEXT_REMOVED
"text updated", // EVENT_TEXT_UPDATED
"text selection changed", // EVENT_TEXT_SELECTION_CHANGED
"visible data changed", // EVENT_VISIBLE_DATA_CHANGED
"text column changed", // EVENT_TEXT_COLUMN_CHANGED
"section changed", // EVENT_SECTION_CHANGED
"table caption changed", // EVENT_TABLE_CAPTION_CHANGED
"table model changed", // EVENT_TABLE_MODEL_CHANGED
"table summary changed", // EVENT_TABLE_SUMMARY_CHANGED
"table row description changed", // EVENT_TABLE_ROW_DESCRIPTION_CHANGED
"table row header changed", // EVENT_TABLE_ROW_HEADER_CHANGED
"table row insert", // EVENT_TABLE_ROW_INSERT
"table row delete", // EVENT_TABLE_ROW_DELETE
"table row reorder", // EVENT_TABLE_ROW_REORDER
"table column description changed", // EVENT_TABLE_COLUMN_DESCRIPTION_CHANGED
"table column header changed", // EVENT_TABLE_COLUMN_HEADER_CHANGED
"table column insert", // EVENT_TABLE_COLUMN_INSERT
"table column delete", // EVENT_TABLE_COLUMN_DELETE
"table column reorder", // EVENT_TABLE_COLUMN_REORDER
"window activate", // EVENT_WINDOW_ACTIVATE
"window create", // EVENT_WINDOW_CREATE
"window deactivate", // EVENT_WINDOW_DEACTIVATE
"window destroy", // EVENT_WINDOW_DESTROY
"window maximize", // EVENT_WINDOW_MAXIMIZE
"window minimize", // EVENT_WINDOW_MINIMIZE
"window resize", // EVENT_WINDOW_RESIZE
"window restore", // EVENT_WINDOW_RESTORE
"hyperlink end index changed", // EVENT_HYPERLINK_END_INDEX_CHANGED
"hyperlink number of anchors changed", // EVENT_HYPERLINK_NUMBER_OF_ANCHORS_CHANGED
"hyperlink selected link changed", // EVENT_HYPERLINK_SELECTED_LINK_CHANGED
"hypertext link activated", // EVENT_HYPERTEXT_LINK_ACTIVATED
"hypertext link selected", // EVENT_HYPERTEXT_LINK_SELECTED
"hyperlink start index changed", // EVENT_HYPERLINK_START_INDEX_CHANGED
"hypertext changed", // EVENT_HYPERTEXT_CHANGED
"hypertext links count changed", // EVENT_HYPERTEXT_NLINKS_CHANGED
"object attribute changed", // EVENT_OBJECT_ATTRIBUTE_CHANGED
"virtual cursor changed", // EVENT_VIRTUALCURSOR_CHANGED
"text value change", // EVENT_TEXT_VALUE_CHANGE
};
#endif

View file

@ -0,0 +1,924 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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 "nsAccessiblePivot.h"
#include "HyperTextAccessible.h"
#include "nsAccUtils.h"
#include "States.h"
#include "xpcAccessibleDocument.h"
using namespace mozilla::a11y;
/**
* An object that stores a given traversal rule during the pivot movement.
*/
class RuleCache
{
public:
explicit RuleCache(nsIAccessibleTraversalRule* aRule) : mRule(aRule),
mAcceptRoles(nullptr) { }
~RuleCache () {
if (mAcceptRoles)
free(mAcceptRoles);
}
nsresult ApplyFilter(Accessible* aAccessible, uint16_t* aResult);
private:
nsCOMPtr<nsIAccessibleTraversalRule> mRule;
uint32_t* mAcceptRoles;
uint32_t mAcceptRolesLength;
uint32_t mPreFilter;
};
////////////////////////////////////////////////////////////////////////////////
// nsAccessiblePivot
nsAccessiblePivot::nsAccessiblePivot(Accessible* aRoot) :
mRoot(aRoot), mModalRoot(nullptr), mPosition(nullptr),
mStartOffset(-1), mEndOffset(-1)
{
NS_ASSERTION(aRoot, "A root accessible is required");
}
nsAccessiblePivot::~nsAccessiblePivot()
{
}
////////////////////////////////////////////////////////////////////////////////
// nsISupports
NS_IMPL_CYCLE_COLLECTION(nsAccessiblePivot, mRoot, mPosition, mObservers)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccessiblePivot)
NS_INTERFACE_MAP_ENTRY(nsIAccessiblePivot)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessiblePivot)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccessiblePivot)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccessiblePivot)
////////////////////////////////////////////////////////////////////////////////
// nsIAccessiblePivot
NS_IMETHODIMP
nsAccessiblePivot::GetRoot(nsIAccessible** aRoot)
{
NS_ENSURE_ARG_POINTER(aRoot);
NS_IF_ADDREF(*aRoot = ToXPC(mRoot));
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::GetPosition(nsIAccessible** aPosition)
{
NS_ENSURE_ARG_POINTER(aPosition);
NS_IF_ADDREF(*aPosition = ToXPC(mPosition));
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::SetPosition(nsIAccessible* aPosition)
{
RefPtr<Accessible> position = nullptr;
if (aPosition) {
position = aPosition->ToInternalAccessible();
if (!position || !IsDescendantOf(position, GetActiveRoot()))
return NS_ERROR_INVALID_ARG;
}
// Swap old position with new position, saves us an AddRef/Release.
mPosition.swap(position);
int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
mStartOffset = mEndOffset = -1;
NotifyOfPivotChange(position, oldStart, oldEnd,
nsIAccessiblePivot::REASON_NONE, false);
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::GetModalRoot(nsIAccessible** aModalRoot)
{
NS_ENSURE_ARG_POINTER(aModalRoot);
NS_IF_ADDREF(*aModalRoot = ToXPC(mModalRoot));
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::SetModalRoot(nsIAccessible* aModalRoot)
{
Accessible* modalRoot = nullptr;
if (aModalRoot) {
modalRoot = aModalRoot->ToInternalAccessible();
if (!modalRoot || !IsDescendantOf(modalRoot, mRoot))
return NS_ERROR_INVALID_ARG;
}
mModalRoot = modalRoot;
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::GetStartOffset(int32_t* aStartOffset)
{
NS_ENSURE_ARG_POINTER(aStartOffset);
*aStartOffset = mStartOffset;
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::GetEndOffset(int32_t* aEndOffset)
{
NS_ENSURE_ARG_POINTER(aEndOffset);
*aEndOffset = mEndOffset;
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::SetTextRange(nsIAccessibleText* aTextAccessible,
int32_t aStartOffset, int32_t aEndOffset,
bool aIsFromUserInput, uint8_t aArgc)
{
NS_ENSURE_ARG(aTextAccessible);
// Check that start offset is smaller than end offset, and that if a value is
// smaller than 0, both should be -1.
NS_ENSURE_TRUE(aStartOffset <= aEndOffset &&
(aStartOffset >= 0 || (aStartOffset != -1 && aEndOffset != -1)),
NS_ERROR_INVALID_ARG);
nsCOMPtr<nsIAccessible> xpcAcc = do_QueryInterface(aTextAccessible);
NS_ENSURE_ARG(xpcAcc);
RefPtr<Accessible> acc = xpcAcc->ToInternalAccessible();
NS_ENSURE_ARG(acc);
HyperTextAccessible* position = acc->AsHyperText();
if (!position || !IsDescendantOf(position, GetActiveRoot()))
return NS_ERROR_INVALID_ARG;
// Make sure the given offsets don't exceed the character count.
if (aEndOffset > static_cast<int32_t>(position->CharacterCount()))
return NS_ERROR_FAILURE;
int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
mStartOffset = aStartOffset;
mEndOffset = aEndOffset;
mPosition.swap(acc);
NotifyOfPivotChange(acc, oldStart, oldEnd,
nsIAccessiblePivot::REASON_TEXT,
(aArgc > 0) ? aIsFromUserInput : true);
return NS_OK;
}
// Traversal functions
NS_IMETHODIMP
nsAccessiblePivot::MoveNext(nsIAccessibleTraversalRule* aRule,
nsIAccessible* aAnchor, bool aIncludeStart,
bool aIsFromUserInput, uint8_t aArgc, bool* aResult)
{
NS_ENSURE_ARG(aResult);
NS_ENSURE_ARG(aRule);
*aResult = false;
Accessible* anchor = mPosition;
if (aArgc > 0 && aAnchor)
anchor = aAnchor->ToInternalAccessible();
if (anchor && (anchor->IsDefunct() || !IsDescendantOf(anchor, GetActiveRoot())))
return NS_ERROR_NOT_IN_TREE;
nsresult rv = NS_OK;
Accessible* accessible =
SearchForward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (accessible)
*aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_NEXT,
(aArgc > 2) ? aIsFromUserInput : true);
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::MovePrevious(nsIAccessibleTraversalRule* aRule,
nsIAccessible* aAnchor,
bool aIncludeStart, bool aIsFromUserInput,
uint8_t aArgc, bool* aResult)
{
NS_ENSURE_ARG(aResult);
NS_ENSURE_ARG(aRule);
*aResult = false;
Accessible* anchor = mPosition;
if (aArgc > 0 && aAnchor)
anchor = aAnchor->ToInternalAccessible();
if (anchor && (anchor->IsDefunct() || !IsDescendantOf(anchor, GetActiveRoot())))
return NS_ERROR_NOT_IN_TREE;
nsresult rv = NS_OK;
Accessible* accessible =
SearchBackward(anchor, aRule, (aArgc > 1) ? aIncludeStart : false, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (accessible)
*aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_PREV,
(aArgc > 2) ? aIsFromUserInput : true);
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::MoveFirst(nsIAccessibleTraversalRule* aRule,
bool aIsFromUserInput,
uint8_t aArgc, bool* aResult)
{
NS_ENSURE_ARG(aResult);
NS_ENSURE_ARG(aRule);
Accessible* root = GetActiveRoot();
NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
nsresult rv = NS_OK;
Accessible* accessible = SearchForward(root, aRule, true, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (accessible)
*aResult = MovePivotInternal(accessible, nsIAccessiblePivot::REASON_FIRST,
(aArgc > 0) ? aIsFromUserInput : true);
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::MoveLast(nsIAccessibleTraversalRule* aRule,
bool aIsFromUserInput,
uint8_t aArgc, bool* aResult)
{
NS_ENSURE_ARG(aResult);
NS_ENSURE_ARG(aRule);
Accessible* root = GetActiveRoot();
NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
*aResult = false;
nsresult rv = NS_OK;
Accessible* lastAccessible = root;
Accessible* accessible = nullptr;
// First go to the last accessible in pre-order
while (lastAccessible->HasChildren())
lastAccessible = lastAccessible->LastChild();
// Search backwards from last accessible and find the last occurrence in the doc
accessible = SearchBackward(lastAccessible, aRule, true, &rv);
NS_ENSURE_SUCCESS(rv, rv);
if (accessible)
*aResult = MovePivotInternal(accessible, nsAccessiblePivot::REASON_LAST,
(aArgc > 0) ? aIsFromUserInput : true);
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::MoveNextByText(TextBoundaryType aBoundary,
bool aIsFromUserInput, uint8_t aArgc,
bool* aResult)
{
NS_ENSURE_ARG(aResult);
*aResult = false;
int32_t tempStart = mStartOffset, tempEnd = mEndOffset;
Accessible* tempPosition = mPosition;
Accessible* root = GetActiveRoot();
while (true) {
Accessible* curPosition = tempPosition;
HyperTextAccessible* text = nullptr;
// Find the nearest text node using a preorder traversal starting from
// the current node.
if (!(text = tempPosition->AsHyperText())) {
text = SearchForText(tempPosition, false);
if (!text)
return NS_OK;
if (text != curPosition)
tempStart = tempEnd = -1;
tempPosition = text;
}
// If the search led to the parent of the node we started on (e.g. when
// starting on a text leaf), start the text movement from the end of that
// node, otherwise we just default to 0.
if (tempEnd == -1)
tempEnd = text == curPosition->Parent() ?
text->GetChildOffset(curPosition) : 0;
// If there's no more text on the current node, try to find the next text
// node; if there isn't one, bail out.
if (tempEnd == static_cast<int32_t>(text->CharacterCount())) {
if (tempPosition == root)
return NS_OK;
// If we're currently sitting on a link, try move to either the next
// sibling or the parent, whichever is closer to the current end
// offset. Otherwise, do a forward search for the next node to land on
// (we don't do this in the first case because we don't want to go to the
// subtree).
Accessible* sibling = tempPosition->NextSibling();
if (tempPosition->IsLink()) {
if (sibling && sibling->IsLink()) {
tempStart = tempEnd = -1;
tempPosition = sibling;
} else {
tempStart = tempPosition->StartOffset();
tempEnd = tempPosition->EndOffset();
tempPosition = tempPosition->Parent();
}
} else {
tempPosition = SearchForText(tempPosition, false);
if (!tempPosition)
return NS_OK;
tempStart = tempEnd = -1;
}
continue;
}
AccessibleTextBoundary startBoundary, endBoundary;
switch (aBoundary) {
case CHAR_BOUNDARY:
startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
break;
case WORD_BOUNDARY:
startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
break;
default:
return NS_ERROR_INVALID_ARG;
}
nsAutoString unusedText;
int32_t newStart = 0, newEnd = 0, currentEnd = tempEnd;
text->TextAtOffset(tempEnd, endBoundary, &newStart, &tempEnd, unusedText);
text->TextBeforeOffset(tempEnd, startBoundary, &newStart, &newEnd, unusedText);
int32_t potentialStart = newEnd == tempEnd ? newStart : newEnd;
tempStart = potentialStart > tempStart ? potentialStart : currentEnd;
// The offset range we've obtained might have embedded characters in it,
// limit the range to the start of the first occurrence of an embedded
// character.
Accessible* childAtOffset = nullptr;
for (int32_t i = tempStart; i < tempEnd; i++) {
childAtOffset = text->GetChildAtOffset(i);
if (childAtOffset && !childAtOffset->IsText()) {
tempEnd = i;
break;
}
}
// If there's an embedded character at the very start of the range, we
// instead want to traverse into it. So restart the movement with
// the child as the starting point.
if (childAtOffset && !childAtOffset->IsText() &&
tempStart == static_cast<int32_t>(childAtOffset->StartOffset())) {
tempPosition = childAtOffset;
tempStart = tempEnd = -1;
continue;
}
*aResult = true;
Accessible* startPosition = mPosition;
int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
mPosition = tempPosition;
mStartOffset = tempStart;
mEndOffset = tempEnd;
NotifyOfPivotChange(startPosition, oldStart, oldEnd,
nsIAccessiblePivot::REASON_TEXT,
(aArgc > 0) ? aIsFromUserInput : true);
return NS_OK;
}
}
NS_IMETHODIMP
nsAccessiblePivot::MovePreviousByText(TextBoundaryType aBoundary,
bool aIsFromUserInput, uint8_t aArgc,
bool* aResult)
{
NS_ENSURE_ARG(aResult);
*aResult = false;
int32_t tempStart = mStartOffset, tempEnd = mEndOffset;
Accessible* tempPosition = mPosition;
Accessible* root = GetActiveRoot();
while (true) {
Accessible* curPosition = tempPosition;
HyperTextAccessible* text;
// Find the nearest text node using a reverse preorder traversal starting
// from the current node.
if (!(text = tempPosition->AsHyperText())) {
text = SearchForText(tempPosition, true);
if (!text)
return NS_OK;
if (text != curPosition)
tempStart = tempEnd = -1;
tempPosition = text;
}
// If the search led to the parent of the node we started on (e.g. when
// starting on a text leaf), start the text movement from the end of that
// node, otherwise we just default to 0.
if (tempStart == -1) {
if (tempPosition != curPosition)
tempStart = text == curPosition->Parent() ?
text->GetChildOffset(curPosition) : text->CharacterCount();
else
tempStart = 0;
}
// If there's no more text on the current node, try to find the previous
// text node; if there isn't one, bail out.
if (tempStart == 0) {
if (tempPosition == root)
return NS_OK;
// If we're currently sitting on a link, try move to either the previous
// sibling or the parent, whichever is closer to the current end
// offset. Otherwise, do a forward search for the next node to land on
// (we don't do this in the first case because we don't want to go to the
// subtree).
Accessible* sibling = tempPosition->PrevSibling();
if (tempPosition->IsLink()) {
if (sibling && sibling->IsLink()) {
HyperTextAccessible* siblingText = sibling->AsHyperText();
tempStart = tempEnd = siblingText ?
siblingText->CharacterCount() : -1;
tempPosition = sibling;
} else {
tempStart = tempPosition->StartOffset();
tempEnd = tempPosition->EndOffset();
tempPosition = tempPosition->Parent();
}
} else {
HyperTextAccessible* tempText = SearchForText(tempPosition, true);
if (!tempText)
return NS_OK;
tempPosition = tempText;
tempStart = tempEnd = tempText->CharacterCount();
}
continue;
}
AccessibleTextBoundary startBoundary, endBoundary;
switch (aBoundary) {
case CHAR_BOUNDARY:
startBoundary = nsIAccessibleText::BOUNDARY_CHAR;
endBoundary = nsIAccessibleText::BOUNDARY_CHAR;
break;
case WORD_BOUNDARY:
startBoundary = nsIAccessibleText::BOUNDARY_WORD_START;
endBoundary = nsIAccessibleText::BOUNDARY_WORD_END;
break;
default:
return NS_ERROR_INVALID_ARG;
}
nsAutoString unusedText;
int32_t newStart = 0, newEnd = 0, currentStart = tempStart, potentialEnd = 0;
text->TextBeforeOffset(tempStart, startBoundary, &newStart, &newEnd, unusedText);
if (newStart < tempStart)
tempStart = newEnd >= currentStart ? newStart : newEnd;
else // XXX: In certain odd cases newStart is equal to tempStart
text->TextBeforeOffset(tempStart - 1, startBoundary, &newStart,
&tempStart, unusedText);
text->TextAtOffset(tempStart, endBoundary, &newStart, &potentialEnd,
unusedText);
tempEnd = potentialEnd < tempEnd ? potentialEnd : currentStart;
// The offset range we've obtained might have embedded characters in it,
// limit the range to the start of the last occurrence of an embedded
// character.
Accessible* childAtOffset = nullptr;
for (int32_t i = tempEnd - 1; i >= tempStart; i--) {
childAtOffset = text->GetChildAtOffset(i);
if (childAtOffset && !childAtOffset->IsText()) {
tempStart = childAtOffset->EndOffset();
break;
}
}
// If there's an embedded character at the very end of the range, we
// instead want to traverse into it. So restart the movement with
// the child as the starting point.
if (childAtOffset && !childAtOffset->IsText() &&
tempEnd == static_cast<int32_t>(childAtOffset->EndOffset())) {
tempPosition = childAtOffset;
tempStart = tempEnd = childAtOffset->AsHyperText()->CharacterCount();
continue;
}
*aResult = true;
Accessible* startPosition = mPosition;
int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
mPosition = tempPosition;
mStartOffset = tempStart;
mEndOffset = tempEnd;
NotifyOfPivotChange(startPosition, oldStart, oldEnd,
nsIAccessiblePivot::REASON_TEXT,
(aArgc > 0) ? aIsFromUserInput : true);
return NS_OK;
}
}
NS_IMETHODIMP
nsAccessiblePivot::MoveToPoint(nsIAccessibleTraversalRule* aRule,
int32_t aX, int32_t aY, bool aIgnoreNoMatch,
bool aIsFromUserInput, uint8_t aArgc,
bool* aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
NS_ENSURE_ARG_POINTER(aRule);
*aResult = false;
Accessible* root = GetActiveRoot();
NS_ENSURE_TRUE(root && !root->IsDefunct(), NS_ERROR_NOT_IN_TREE);
RuleCache cache(aRule);
Accessible* match = nullptr;
Accessible* child = root->ChildAtPoint(aX, aY, Accessible::eDeepestChild);
while (child && root != child) {
uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
nsresult rv = cache.ApplyFilter(child, &filtered);
NS_ENSURE_SUCCESS(rv, rv);
// Ignore any matching nodes that were below this one
if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE)
match = nullptr;
// Match if no node below this is a match
if ((filtered & nsIAccessibleTraversalRule::FILTER_MATCH) && !match) {
nsIntRect childRect = child->Bounds();
// Double-check child's bounds since the deepest child may have been out
// of bounds. This assures we don't return a false positive.
if (aX >= childRect.x && aX < childRect.x + childRect.width &&
aY >= childRect.y && aY < childRect.y + childRect.height)
match = child;
}
child = child->Parent();
}
if (match || !aIgnoreNoMatch)
*aResult = MovePivotInternal(match, nsIAccessiblePivot::REASON_POINT,
(aArgc > 0) ? aIsFromUserInput : true);
return NS_OK;
}
// Observer functions
NS_IMETHODIMP
nsAccessiblePivot::AddObserver(nsIAccessiblePivotObserver* aObserver)
{
NS_ENSURE_ARG(aObserver);
mObservers.AppendElement(aObserver);
return NS_OK;
}
NS_IMETHODIMP
nsAccessiblePivot::RemoveObserver(nsIAccessiblePivotObserver* aObserver)
{
NS_ENSURE_ARG(aObserver);
return mObservers.RemoveElement(aObserver) ? NS_OK : NS_ERROR_FAILURE;
}
// Private utility methods
bool
nsAccessiblePivot::IsDescendantOf(Accessible* aAccessible, Accessible* aAncestor)
{
if (!aAncestor || aAncestor->IsDefunct())
return false;
// XXX Optimize with IsInDocument() when appropriate. Blocked by bug 759875.
Accessible* accessible = aAccessible;
do {
if (accessible == aAncestor)
return true;
} while ((accessible = accessible->Parent()));
return false;
}
bool
nsAccessiblePivot::MovePivotInternal(Accessible* aPosition,
PivotMoveReason aReason,
bool aIsFromUserInput)
{
RefPtr<Accessible> oldPosition = mPosition.forget();
mPosition = aPosition;
int32_t oldStart = mStartOffset, oldEnd = mEndOffset;
mStartOffset = mEndOffset = -1;
return NotifyOfPivotChange(oldPosition, oldStart, oldEnd, aReason,
aIsFromUserInput);
}
Accessible*
nsAccessiblePivot::AdjustStartPosition(Accessible* aAccessible,
RuleCache& aCache,
uint16_t* aFilterResult,
nsresult* aResult)
{
Accessible* matched = aAccessible;
*aResult = aCache.ApplyFilter(aAccessible, aFilterResult);
if (aAccessible != mRoot && aAccessible != mModalRoot) {
for (Accessible* temp = aAccessible->Parent();
temp && temp != mRoot && temp != mModalRoot; temp = temp->Parent()) {
uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
*aResult = aCache.ApplyFilter(temp, &filtered);
NS_ENSURE_SUCCESS(*aResult, nullptr);
if (filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) {
*aFilterResult = filtered;
matched = temp;
}
}
}
if (aAccessible == mPosition && mStartOffset != -1 && mEndOffset != -1) {
HyperTextAccessible* text = aAccessible->AsHyperText();
if (text) {
matched = text->GetChildAtOffset(mStartOffset);
}
}
return matched;
}
Accessible*
nsAccessiblePivot::SearchBackward(Accessible* aAccessible,
nsIAccessibleTraversalRule* aRule,
bool aSearchCurrent,
nsresult* aResult)
{
*aResult = NS_OK;
// Initial position could be unset, in that case return null.
if (!aAccessible)
return nullptr;
RuleCache cache(aRule);
uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
Accessible* accessible = AdjustStartPosition(aAccessible, cache,
&filtered, aResult);
NS_ENSURE_SUCCESS(*aResult, nullptr);
if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)) {
return accessible;
}
Accessible* root = GetActiveRoot();
while (accessible != root) {
Accessible* parent = accessible->Parent();
int32_t idxInParent = accessible->IndexInParent();
while (idxInParent > 0) {
if (!(accessible = parent->GetChildAt(--idxInParent)))
continue;
*aResult = cache.ApplyFilter(accessible, &filtered);
NS_ENSURE_SUCCESS(*aResult, nullptr);
Accessible* lastChild = nullptr;
while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
(lastChild = accessible->LastChild())) {
parent = accessible;
accessible = lastChild;
idxInParent = accessible->IndexInParent();
*aResult = cache.ApplyFilter(accessible, &filtered);
NS_ENSURE_SUCCESS(*aResult, nullptr);
}
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
return accessible;
}
if (!(accessible = parent))
break;
*aResult = cache.ApplyFilter(accessible, &filtered);
NS_ENSURE_SUCCESS(*aResult, nullptr);
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
return accessible;
}
return nullptr;
}
Accessible*
nsAccessiblePivot::SearchForward(Accessible* aAccessible,
nsIAccessibleTraversalRule* aRule,
bool aSearchCurrent,
nsresult* aResult)
{
*aResult = NS_OK;
// Initial position could be not set, in that case begin search from root.
Accessible* root = GetActiveRoot();
Accessible* accessible = (!aAccessible) ? root : aAccessible;
RuleCache cache(aRule);
uint16_t filtered = nsIAccessibleTraversalRule::FILTER_IGNORE;
accessible = AdjustStartPosition(accessible, cache, &filtered, aResult);
NS_ENSURE_SUCCESS(*aResult, nullptr);
if (aSearchCurrent && (filtered & nsIAccessibleTraversalRule::FILTER_MATCH))
return accessible;
while (true) {
Accessible* firstChild = nullptr;
while (!(filtered & nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE) &&
(firstChild = accessible->FirstChild())) {
accessible = firstChild;
*aResult = cache.ApplyFilter(accessible, &filtered);
NS_ENSURE_SUCCESS(*aResult, nullptr);
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
return accessible;
}
Accessible* sibling = nullptr;
Accessible* temp = accessible;
do {
if (temp == root)
break;
sibling = temp->NextSibling();
if (sibling)
break;
} while ((temp = temp->Parent()));
if (!sibling)
break;
accessible = sibling;
*aResult = cache.ApplyFilter(accessible, &filtered);
NS_ENSURE_SUCCESS(*aResult, nullptr);
if (filtered & nsIAccessibleTraversalRule::FILTER_MATCH)
return accessible;
}
return nullptr;
}
HyperTextAccessible*
nsAccessiblePivot::SearchForText(Accessible* aAccessible, bool aBackward)
{
Accessible* root = GetActiveRoot();
Accessible* accessible = aAccessible;
while (true) {
Accessible* child = nullptr;
while ((child = (aBackward ? accessible->LastChild() :
accessible->FirstChild()))) {
accessible = child;
if (child->IsHyperText())
return child->AsHyperText();
}
Accessible* sibling = nullptr;
Accessible* temp = accessible;
do {
if (temp == root)
break;
if (temp != aAccessible && temp->IsHyperText())
return temp->AsHyperText();
sibling = aBackward ? temp->PrevSibling() : temp->NextSibling();
if (sibling)
break;
} while ((temp = temp->Parent()));
if (!sibling)
break;
accessible = sibling;
if (accessible->IsHyperText())
return accessible->AsHyperText();
}
return nullptr;
}
bool
nsAccessiblePivot::NotifyOfPivotChange(Accessible* aOldPosition,
int32_t aOldStart, int32_t aOldEnd,
int16_t aReason, bool aIsFromUserInput)
{
if (aOldPosition == mPosition &&
aOldStart == mStartOffset && aOldEnd == mEndOffset)
return false;
nsCOMPtr<nsIAccessible> xpcOldPos = ToXPC(aOldPosition); // death grip
nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> >::ForwardIterator iter(mObservers);
while (iter.HasMore()) {
nsIAccessiblePivotObserver* obs = iter.GetNext();
obs->OnPivotChanged(this, xpcOldPos, aOldStart, aOldEnd, aReason,
aIsFromUserInput);
}
return true;
}
nsresult
RuleCache::ApplyFilter(Accessible* aAccessible, uint16_t* aResult)
{
*aResult = nsIAccessibleTraversalRule::FILTER_IGNORE;
if (!mAcceptRoles) {
nsresult rv = mRule->GetMatchRoles(&mAcceptRoles, &mAcceptRolesLength);
NS_ENSURE_SUCCESS(rv, rv);
rv = mRule->GetPreFilter(&mPreFilter);
NS_ENSURE_SUCCESS(rv, rv);
}
if (mPreFilter) {
uint64_t state = aAccessible->State();
if ((nsIAccessibleTraversalRule::PREFILTER_INVISIBLE & mPreFilter) &&
(state & states::INVISIBLE))
return NS_OK;
if ((nsIAccessibleTraversalRule::PREFILTER_OFFSCREEN & mPreFilter) &&
(state & states::OFFSCREEN))
return NS_OK;
if ((nsIAccessibleTraversalRule::PREFILTER_NOT_FOCUSABLE & mPreFilter) &&
!(state & states::FOCUSABLE))
return NS_OK;
if (nsIAccessibleTraversalRule::PREFILTER_ARIA_HIDDEN & mPreFilter) {
if (aAccessible->IsARIAHidden()) {
*aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
return NS_OK;
}
}
if ((nsIAccessibleTraversalRule::PREFILTER_TRANSPARENT & mPreFilter) &&
!(state & states::OPAQUE1)) {
nsIFrame* frame = aAccessible->GetFrame();
if (frame->StyleEffects()->mOpacity == 0.0f) {
*aResult |= nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
return NS_OK;
}
}
}
if (mAcceptRolesLength > 0) {
uint32_t accessibleRole = aAccessible->Role();
bool matchesRole = false;
for (uint32_t idx = 0; idx < mAcceptRolesLength; idx++) {
matchesRole = mAcceptRoles[idx] == accessibleRole;
if (matchesRole)
break;
}
if (!matchesRole)
return NS_OK;
}
return mRule->Match(ToXPC(aAccessible), aResult);
}

View file

@ -0,0 +1,144 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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/. */
#ifndef _nsAccessiblePivot_H_
#define _nsAccessiblePivot_H_
#include "nsIAccessiblePivot.h"
#include "Accessible-inl.h"
#include "nsTObserverArray.h"
#include "nsCycleCollectionParticipant.h"
#include "mozilla/Attributes.h"
class RuleCache;
/**
* Class represents an accessible pivot.
*/
class nsAccessiblePivot final : public nsIAccessiblePivot
{
public:
typedef mozilla::a11y::Accessible Accessible;
explicit nsAccessiblePivot(Accessible* aRoot);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsAccessiblePivot, nsIAccessiblePivot)
NS_DECL_NSIACCESSIBLEPIVOT
/*
* A simple getter for the pivot's position.
*/
Accessible* Position() { return mPosition; }
private:
~nsAccessiblePivot();
nsAccessiblePivot() = delete;
nsAccessiblePivot(const nsAccessiblePivot&) = delete;
void operator = (const nsAccessiblePivot&) = delete;
/*
* Notify all observers on a pivot change. Return true if it has changed and
* observers have been notified.
*/
bool NotifyOfPivotChange(Accessible* aOldAccessible,
int32_t aOldStart, int32_t aOldEnd,
PivotMoveReason aReason,
bool aIsFromUserInput);
/*
* Check to see that the given accessible is a descendant of given ancestor
*/
bool IsDescendantOf(Accessible* aAccessible, Accessible* aAncestor);
/*
* Search in preorder for the first accessible to match the rule.
*/
Accessible* SearchForward(Accessible* aAccessible,
nsIAccessibleTraversalRule* aRule,
bool aSearchCurrent,
nsresult* aResult);
/*
* Reverse search in preorder for the first accessible to match the rule.
*/
Accessible* SearchBackward(Accessible* aAccessible,
nsIAccessibleTraversalRule* aRule,
bool aSearchCurrent,
nsresult* aResult);
/*
* Search in preorder for the first text accessible.
*/
mozilla::a11y::HyperTextAccessible* SearchForText(Accessible* aAccessible,
bool aBackward);
/*
* Get the effective root for this pivot, either the true root or modal root.
*/
Accessible* GetActiveRoot() const
{
if (mModalRoot) {
NS_ENSURE_FALSE(mModalRoot->IsDefunct(), mRoot);
return mModalRoot;
}
return mRoot;
}
/*
* Update the pivot, and notify observers. Return true if it moved.
*/
bool MovePivotInternal(Accessible* aPosition, PivotMoveReason aReason,
bool aIsFromUserInput);
/*
* Get initial node we should start a search from with a given rule.
*
* When we do a move operation from one position to another,
* the initial position can be inside of a subtree that is ignored by
* the given rule. We need to step out of the ignored subtree and start
* the search from there.
*
*/
Accessible* AdjustStartPosition(Accessible* aAccessible, RuleCache& aCache,
uint16_t* aFilterResult, nsresult* aResult);
/*
* The root accessible.
*/
RefPtr<Accessible> mRoot;
/*
* The temporary modal root accessible.
*/
RefPtr<Accessible> mModalRoot;
/*
* The current pivot position.
*/
RefPtr<Accessible> mPosition;
/*
* The text start offset ofthe pivot.
*/
int32_t mStartOffset;
/*
* The text end offset ofthe pivot.
*/
int32_t mEndOffset;
/*
* The list of pivot-changed observers.
*/
nsTObserverArray<nsCOMPtr<nsIAccessiblePivotObserver> > mObservers;
};
#endif

View file

@ -0,0 +1,683 @@
/* -*- 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 "nsCoreUtils.h"
#include "nsIAccessibleTypes.h"
#include "nsIBaseWindow.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIDocument.h"
#include "nsIDOMHTMLDocument.h"
#include "nsIDOMHTMLElement.h"
#include "nsRange.h"
#include "nsIBoxObject.h"
#include "nsIDOMXULElement.h"
#include "nsIDocShell.h"
#include "nsIObserverService.h"
#include "nsIPresShell.h"
#include "nsPresContext.h"
#include "nsIScrollableFrame.h"
#include "nsISelectionPrivate.h"
#include "nsISelectionController.h"
#include "nsISimpleEnumerator.h"
#include "mozilla/dom/TouchEvent.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/TouchEvents.h"
#include "nsView.h"
#include "nsGkAtoms.h"
#include "nsComponentManagerUtils.h"
#include "nsITreeBoxObject.h"
#include "nsITreeColumns.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLLabelElement.h"
using namespace mozilla;
////////////////////////////////////////////////////////////////////////////////
// nsCoreUtils
////////////////////////////////////////////////////////////////////////////////
bool
nsCoreUtils::IsLabelWithControl(nsIContent* aContent)
{
dom::HTMLLabelElement* label = dom::HTMLLabelElement::FromContent(aContent);
if (label && label->GetControl())
return true;
return false;
}
bool
nsCoreUtils::HasClickListener(nsIContent *aContent)
{
NS_ENSURE_TRUE(aContent, false);
EventListenerManager* listenerManager =
aContent->GetExistingListenerManager();
return listenerManager &&
(listenerManager->HasListenersFor(nsGkAtoms::onclick) ||
listenerManager->HasListenersFor(nsGkAtoms::onmousedown) ||
listenerManager->HasListenersFor(nsGkAtoms::onmouseup));
}
void
nsCoreUtils::DispatchClickEvent(nsITreeBoxObject *aTreeBoxObj,
int32_t aRowIndex, nsITreeColumn *aColumn,
const nsAString& aPseudoElt)
{
nsCOMPtr<nsIDOMElement> tcElm;
aTreeBoxObj->GetTreeBody(getter_AddRefs(tcElm));
if (!tcElm)
return;
nsCOMPtr<nsIContent> tcContent(do_QueryInterface(tcElm));
nsIDocument *document = tcContent->GetUncomposedDoc();
if (!document)
return;
nsCOMPtr<nsIPresShell> presShell = document->GetShell();
if (!presShell)
return;
// Ensure row is visible.
aTreeBoxObj->EnsureRowIsVisible(aRowIndex);
// Calculate x and y coordinates.
int32_t x = 0, y = 0, width = 0, height = 0;
nsresult rv = aTreeBoxObj->GetCoordsForCellItem(aRowIndex, aColumn,
aPseudoElt,
&x, &y, &width, &height);
if (NS_FAILED(rv))
return;
nsCOMPtr<nsIDOMXULElement> tcXULElm(do_QueryInterface(tcElm));
nsCOMPtr<nsIBoxObject> tcBoxObj;
tcXULElm->GetBoxObject(getter_AddRefs(tcBoxObj));
int32_t tcX = 0;
tcBoxObj->GetX(&tcX);
int32_t tcY = 0;
tcBoxObj->GetY(&tcY);
// Dispatch mouse events.
nsWeakFrame tcFrame = tcContent->GetPrimaryFrame();
nsIFrame* rootFrame = presShell->GetRootFrame();
nsPoint offset;
nsIWidget *rootWidget =
rootFrame->GetView()->GetNearestWidget(&offset);
RefPtr<nsPresContext> presContext = presShell->GetPresContext();
int32_t cnvdX = presContext->CSSPixelsToDevPixels(tcX + x + 1) +
presContext->AppUnitsToDevPixels(offset.x);
int32_t cnvdY = presContext->CSSPixelsToDevPixels(tcY + y + 1) +
presContext->AppUnitsToDevPixels(offset.y);
// XUL is just desktop, so there is no real reason for senfing touch events.
DispatchMouseEvent(eMouseDown, cnvdX, cnvdY,
tcContent, tcFrame, presShell, rootWidget);
DispatchMouseEvent(eMouseUp, cnvdX, cnvdY,
tcContent, tcFrame, presShell, rootWidget);
}
void
nsCoreUtils::DispatchMouseEvent(EventMessage aMessage, int32_t aX, int32_t aY,
nsIContent *aContent, nsIFrame *aFrame,
nsIPresShell *aPresShell, nsIWidget *aRootWidget)
{
WidgetMouseEvent event(true, aMessage, aRootWidget,
WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
event.mRefPoint = LayoutDeviceIntPoint(aX, aY);
event.mClickCount = 1;
event.button = WidgetMouseEvent::eLeftButton;
event.mTime = PR_IntervalNow();
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
nsEventStatus status = nsEventStatus_eIgnore;
aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
}
void
nsCoreUtils::DispatchTouchEvent(EventMessage aMessage, int32_t aX, int32_t aY,
nsIContent* aContent, nsIFrame* aFrame,
nsIPresShell* aPresShell, nsIWidget* aRootWidget)
{
nsIDocShell* docShell = nullptr;
if (aPresShell->GetDocument()) {
docShell = aPresShell->GetDocument()->GetDocShell();
}
if (!dom::TouchEvent::PrefEnabled(docShell)) {
return;
}
WidgetTouchEvent event(true, aMessage, aRootWidget);
event.mTime = PR_IntervalNow();
// XXX: Touch has an identifier of -1 to hint that it is synthesized.
RefPtr<dom::Touch> t = new dom::Touch(-1, LayoutDeviceIntPoint(aX, aY),
LayoutDeviceIntPoint(1, 1), 0.0f, 1.0f);
t->SetTarget(aContent);
event.mTouches.AppendElement(t);
nsEventStatus status = nsEventStatus_eIgnore;
aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
}
uint32_t
nsCoreUtils::GetAccessKeyFor(nsIContent* aContent)
{
// Accesskeys are registered by @accesskey attribute only. At first check
// whether it is presented on the given element to avoid the slow
// EventStateManager::GetRegisteredAccessKey() method.
if (!aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::accesskey))
return 0;
nsIPresShell* presShell = aContent->OwnerDoc()->GetShell();
if (!presShell)
return 0;
nsPresContext *presContext = presShell->GetPresContext();
if (!presContext)
return 0;
EventStateManager *esm = presContext->EventStateManager();
if (!esm)
return 0;
return esm->GetRegisteredAccessKey(aContent);
}
nsIContent *
nsCoreUtils::GetDOMElementFor(nsIContent *aContent)
{
if (aContent->IsElement())
return aContent;
if (aContent->IsNodeOfType(nsINode::eTEXT))
return aContent->GetFlattenedTreeParent();
return nullptr;
}
nsINode *
nsCoreUtils::GetDOMNodeFromDOMPoint(nsINode *aNode, uint32_t aOffset)
{
if (aNode && aNode->IsElement()) {
uint32_t childCount = aNode->GetChildCount();
NS_ASSERTION(aOffset <= childCount, "Wrong offset of the DOM point!");
// The offset can be after last child of container node that means DOM point
// is placed immediately after the last child. In this case use the DOM node
// from the given DOM point is used as result node.
if (aOffset != childCount)
return aNode->GetChildAt(aOffset);
}
return aNode;
}
bool
nsCoreUtils::IsAncestorOf(nsINode *aPossibleAncestorNode,
nsINode *aPossibleDescendantNode,
nsINode *aRootNode)
{
NS_ENSURE_TRUE(aPossibleAncestorNode && aPossibleDescendantNode, false);
nsINode *parentNode = aPossibleDescendantNode;
while ((parentNode = parentNode->GetParentNode()) &&
parentNode != aRootNode) {
if (parentNode == aPossibleAncestorNode)
return true;
}
return false;
}
nsresult
nsCoreUtils::ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
uint32_t aScrollType)
{
nsIPresShell::ScrollAxis vertical, horizontal;
ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
return ScrollSubstringTo(aFrame, aRange, vertical, horizontal);
}
nsresult
nsCoreUtils::ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
nsIPresShell::ScrollAxis aVertical,
nsIPresShell::ScrollAxis aHorizontal)
{
if (!aFrame)
return NS_ERROR_FAILURE;
nsPresContext *presContext = aFrame->PresContext();
nsCOMPtr<nsISelectionController> selCon;
aFrame->GetSelectionController(presContext, getter_AddRefs(selCon));
NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
nsCOMPtr<nsISelection> selection;
selCon->GetSelection(nsISelectionController::SELECTION_ACCESSIBILITY,
getter_AddRefs(selection));
nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(selection));
selection->RemoveAllRanges();
selection->AddRange(aRange);
privSel->ScrollIntoViewInternal(
nsISelectionController::SELECTION_ANCHOR_REGION,
true, aVertical, aHorizontal);
selection->CollapseToStart();
return NS_OK;
}
void
nsCoreUtils::ScrollFrameToPoint(nsIFrame *aScrollableFrame,
nsIFrame *aFrame,
const nsIntPoint& aPoint)
{
nsIScrollableFrame* scrollableFrame = do_QueryFrame(aScrollableFrame);
if (!scrollableFrame)
return;
nsPoint point =
ToAppUnits(aPoint, aFrame->PresContext()->AppUnitsPerDevPixel());
nsRect frameRect = aFrame->GetScreenRectInAppUnits();
nsPoint deltaPoint(point.x - frameRect.x, point.y - frameRect.y);
nsPoint scrollPoint = scrollableFrame->GetScrollPosition();
scrollPoint -= deltaPoint;
scrollableFrame->ScrollTo(scrollPoint, nsIScrollableFrame::INSTANT);
}
void
nsCoreUtils::ConvertScrollTypeToPercents(uint32_t aScrollType,
nsIPresShell::ScrollAxis *aVertical,
nsIPresShell::ScrollAxis *aHorizontal)
{
int16_t whereY, whereX;
nsIPresShell::WhenToScroll whenY, whenX;
switch (aScrollType)
{
case nsIAccessibleScrollType::SCROLL_TYPE_TOP_LEFT:
whereY = nsIPresShell::SCROLL_TOP;
whenY = nsIPresShell::SCROLL_ALWAYS;
whereX = nsIPresShell::SCROLL_LEFT;
whenX = nsIPresShell::SCROLL_ALWAYS;
break;
case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_RIGHT:
whereY = nsIPresShell::SCROLL_BOTTOM;
whenY = nsIPresShell::SCROLL_ALWAYS;
whereX = nsIPresShell::SCROLL_RIGHT;
whenX = nsIPresShell::SCROLL_ALWAYS;
break;
case nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE:
whereY = nsIPresShell::SCROLL_TOP;
whenY = nsIPresShell::SCROLL_ALWAYS;
whereX = nsIPresShell::SCROLL_MINIMUM;
whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
break;
case nsIAccessibleScrollType::SCROLL_TYPE_BOTTOM_EDGE:
whereY = nsIPresShell::SCROLL_BOTTOM;
whenY = nsIPresShell::SCROLL_ALWAYS;
whereX = nsIPresShell::SCROLL_MINIMUM;
whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
break;
case nsIAccessibleScrollType::SCROLL_TYPE_LEFT_EDGE:
whereY = nsIPresShell::SCROLL_MINIMUM;
whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
whereX = nsIPresShell::SCROLL_LEFT;
whenX = nsIPresShell::SCROLL_ALWAYS;
break;
case nsIAccessibleScrollType::SCROLL_TYPE_RIGHT_EDGE:
whereY = nsIPresShell::SCROLL_MINIMUM;
whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
whereX = nsIPresShell::SCROLL_RIGHT;
whenX = nsIPresShell::SCROLL_ALWAYS;
break;
default:
whereY = nsIPresShell::SCROLL_MINIMUM;
whenY = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
whereX = nsIPresShell::SCROLL_MINIMUM;
whenX = nsIPresShell::SCROLL_IF_NOT_FULLY_VISIBLE;
}
*aVertical = nsIPresShell::ScrollAxis(whereY, whenY);
*aHorizontal = nsIPresShell::ScrollAxis(whereX, whenX);
}
nsIntPoint
nsCoreUtils::GetScreenCoordsForWindow(nsINode *aNode)
{
nsIntPoint coords(0, 0);
nsCOMPtr<nsIDocShellTreeItem> treeItem(GetDocShellFor(aNode));
if (!treeItem)
return coords;
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
treeItem->GetTreeOwner(getter_AddRefs(treeOwner));
if (!treeOwner)
return coords;
nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
if (baseWindow)
baseWindow->GetPosition(&coords.x, &coords.y); // in device pixels
return coords;
}
already_AddRefed<nsIDocShell>
nsCoreUtils::GetDocShellFor(nsINode *aNode)
{
if (!aNode)
return nullptr;
nsCOMPtr<nsIDocShell> docShell = aNode->OwnerDoc()->GetDocShell();
return docShell.forget();
}
bool
nsCoreUtils::IsRootDocument(nsIDocument *aDocument)
{
nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aDocument->GetDocShell();
NS_ASSERTION(docShellTreeItem, "No document shell for document!");
nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
docShellTreeItem->GetParent(getter_AddRefs(parentTreeItem));
return !parentTreeItem;
}
bool
nsCoreUtils::IsContentDocument(nsIDocument *aDocument)
{
nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = aDocument->GetDocShell();
NS_ASSERTION(docShellTreeItem, "No document shell tree item for document!");
return (docShellTreeItem->ItemType() == nsIDocShellTreeItem::typeContent);
}
bool
nsCoreUtils::IsTabDocument(nsIDocument* aDocumentNode)
{
nsCOMPtr<nsIDocShellTreeItem> treeItem(aDocumentNode->GetDocShell());
nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
treeItem->GetParent(getter_AddRefs(parentTreeItem));
// Tab document running in own process doesn't have parent.
if (XRE_IsContentProcess())
return !parentTreeItem;
// Parent of docshell for tab document running in chrome process is root.
nsCOMPtr<nsIDocShellTreeItem> rootTreeItem;
treeItem->GetRootTreeItem(getter_AddRefs(rootTreeItem));
return parentTreeItem == rootTreeItem;
}
bool
nsCoreUtils::IsErrorPage(nsIDocument *aDocument)
{
nsIURI *uri = aDocument->GetDocumentURI();
bool isAboutScheme = false;
uri->SchemeIs("about", &isAboutScheme);
if (!isAboutScheme)
return false;
nsAutoCString path;
uri->GetPath(path);
NS_NAMED_LITERAL_CSTRING(neterror, "neterror");
NS_NAMED_LITERAL_CSTRING(certerror, "certerror");
return StringBeginsWith(path, neterror) || StringBeginsWith(path, certerror);
}
bool
nsCoreUtils::GetID(nsIContent *aContent, nsAString& aID)
{
return aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, aID);
}
bool
nsCoreUtils::GetUIntAttr(nsIContent *aContent, nsIAtom *aAttr, int32_t *aUInt)
{
nsAutoString value;
aContent->GetAttr(kNameSpaceID_None, aAttr, value);
if (!value.IsEmpty()) {
nsresult error = NS_OK;
int32_t integer = value.ToInteger(&error);
if (NS_SUCCEEDED(error) && integer > 0) {
*aUInt = integer;
return true;
}
}
return false;
}
void
nsCoreUtils::GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
nsAString& aLanguage)
{
aLanguage.Truncate();
nsIContent *walkUp = aContent;
while (walkUp && walkUp != aRootContent &&
!walkUp->GetAttr(kNameSpaceID_None, nsGkAtoms::lang, aLanguage))
walkUp = walkUp->GetParent();
}
already_AddRefed<nsIBoxObject>
nsCoreUtils::GetTreeBodyBoxObject(nsITreeBoxObject *aTreeBoxObj)
{
nsCOMPtr<nsIDOMElement> tcElm;
aTreeBoxObj->GetTreeBody(getter_AddRefs(tcElm));
nsCOMPtr<nsIDOMXULElement> tcXULElm(do_QueryInterface(tcElm));
if (!tcXULElm)
return nullptr;
nsCOMPtr<nsIBoxObject> boxObj;
tcXULElm->GetBoxObject(getter_AddRefs(boxObj));
return boxObj.forget();
}
already_AddRefed<nsITreeBoxObject>
nsCoreUtils::GetTreeBoxObject(nsIContent *aContent)
{
// Find DOMNode's parents recursively until reach the <tree> tag
nsIContent* currentContent = aContent;
while (currentContent) {
if (currentContent->NodeInfo()->Equals(nsGkAtoms::tree,
kNameSpaceID_XUL)) {
// We will get the nsITreeBoxObject from the tree node
nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(currentContent));
if (xulElement) {
nsCOMPtr<nsIBoxObject> box;
xulElement->GetBoxObject(getter_AddRefs(box));
nsCOMPtr<nsITreeBoxObject> treeBox(do_QueryInterface(box));
if (treeBox)
return treeBox.forget();
}
}
currentContent = currentContent->GetFlattenedTreeParent();
}
return nullptr;
}
already_AddRefed<nsITreeColumn>
nsCoreUtils::GetFirstSensibleColumn(nsITreeBoxObject *aTree)
{
nsCOMPtr<nsITreeColumns> cols;
aTree->GetColumns(getter_AddRefs(cols));
if (!cols)
return nullptr;
nsCOMPtr<nsITreeColumn> column;
cols->GetFirstColumn(getter_AddRefs(column));
if (column && IsColumnHidden(column))
return GetNextSensibleColumn(column);
return column.forget();
}
uint32_t
nsCoreUtils::GetSensibleColumnCount(nsITreeBoxObject *aTree)
{
uint32_t count = 0;
nsCOMPtr<nsITreeColumns> cols;
aTree->GetColumns(getter_AddRefs(cols));
if (!cols)
return count;
nsCOMPtr<nsITreeColumn> column;
cols->GetFirstColumn(getter_AddRefs(column));
while (column) {
if (!IsColumnHidden(column))
count++;
nsCOMPtr<nsITreeColumn> nextColumn;
column->GetNext(getter_AddRefs(nextColumn));
column.swap(nextColumn);
}
return count;
}
already_AddRefed<nsITreeColumn>
nsCoreUtils::GetSensibleColumnAt(nsITreeBoxObject *aTree, uint32_t aIndex)
{
uint32_t idx = aIndex;
nsCOMPtr<nsITreeColumn> column = GetFirstSensibleColumn(aTree);
while (column) {
if (idx == 0)
return column.forget();
idx--;
column = GetNextSensibleColumn(column);
}
return nullptr;
}
already_AddRefed<nsITreeColumn>
nsCoreUtils::GetNextSensibleColumn(nsITreeColumn *aColumn)
{
nsCOMPtr<nsITreeColumn> nextColumn;
aColumn->GetNext(getter_AddRefs(nextColumn));
while (nextColumn && IsColumnHidden(nextColumn)) {
nsCOMPtr<nsITreeColumn> tempColumn;
nextColumn->GetNext(getter_AddRefs(tempColumn));
nextColumn.swap(tempColumn);
}
return nextColumn.forget();
}
already_AddRefed<nsITreeColumn>
nsCoreUtils::GetPreviousSensibleColumn(nsITreeColumn *aColumn)
{
nsCOMPtr<nsITreeColumn> prevColumn;
aColumn->GetPrevious(getter_AddRefs(prevColumn));
while (prevColumn && IsColumnHidden(prevColumn)) {
nsCOMPtr<nsITreeColumn> tempColumn;
prevColumn->GetPrevious(getter_AddRefs(tempColumn));
prevColumn.swap(tempColumn);
}
return prevColumn.forget();
}
bool
nsCoreUtils::IsColumnHidden(nsITreeColumn *aColumn)
{
nsCOMPtr<nsIDOMElement> element;
aColumn->GetElement(getter_AddRefs(element));
nsCOMPtr<nsIContent> content = do_QueryInterface(element);
return content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
nsGkAtoms::_true, eCaseMatters);
}
void
nsCoreUtils::ScrollTo(nsIPresShell* aPresShell, nsIContent* aContent,
uint32_t aScrollType)
{
nsIPresShell::ScrollAxis vertical, horizontal;
ConvertScrollTypeToPercents(aScrollType, &vertical, &horizontal);
aPresShell->ScrollContentIntoView(aContent, vertical, horizontal,
nsIPresShell::SCROLL_OVERFLOW_HIDDEN);
}
bool
nsCoreUtils::IsWhitespaceString(const nsSubstring& aString)
{
nsSubstring::const_char_iterator iterBegin, iterEnd;
aString.BeginReading(iterBegin);
aString.EndReading(iterEnd);
while (iterBegin != iterEnd && IsWhitespace(*iterBegin))
++iterBegin;
return iterBegin == iterEnd;
}
bool
nsCoreUtils::AccEventObserversExist()
{
nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
NS_ENSURE_TRUE(obsService, false);
nsCOMPtr<nsISimpleEnumerator> observers;
obsService->EnumerateObservers(NS_ACCESSIBLE_EVENT_TOPIC,
getter_AddRefs(observers));
NS_ENSURE_TRUE(observers, false);
bool hasObservers = false;
observers->HasMoreElements(&hasObservers);
return hasObservers;
}
void
nsCoreUtils::DispatchAccEvent(RefPtr<nsIAccessibleEvent> event)
{
nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
NS_ENSURE_TRUE_VOID(obsService);
obsService->NotifyObservers(event, NS_ACCESSIBLE_EVENT_TOPIC, nullptr);
}
void
nsCoreUtils::XBLBindingRole(const nsIContent* aEl, nsAString& aRole)
{
for (const nsXBLBinding* binding = aEl->GetXBLBinding(); binding;
binding = binding->GetBaseBinding()) {
nsIContent* bindingElm = binding->PrototypeBinding()->GetBindingElement();
bindingElm->GetAttr(kNameSpaceID_None, nsGkAtoms::role, aRole);
if (!aRole.IsEmpty())
break;
}
}

View file

@ -0,0 +1,329 @@
/* -*- 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/. */
#ifndef nsCoreUtils_h_
#define nsCoreUtils_h_
#include "mozilla/EventForwards.h"
#include "nsIAccessibleEvent.h"
#include "nsIContent.h"
#include "nsIDocument.h" // for GetShell()
#include "nsIPresShell.h"
#include "nsPoint.h"
#include "nsTArray.h"
class nsRange;
class nsIBoxObject;
class nsIFrame;
class nsIDocShell;
class nsITreeColumn;
class nsITreeBoxObject;
class nsIWidget;
/**
* Core utils.
*/
class nsCoreUtils
{
public:
/**
* Return true if the given node is a label of a control.
*/
static bool IsLabelWithControl(nsIContent *aContent);
/**
* Return true if the given node has registered click, mousedown or mouseup
* event listeners.
*/
static bool HasClickListener(nsIContent *aContent);
/**
* Dispatch click event to XUL tree cell.
*
* @param aTreeBoxObj [in] tree box object
* @param aRowIndex [in] row index
* @param aColumn [in] column object
* @param aPseudoElm [in] pseudo elemenet inside the cell, see
* nsITreeBoxObject for available values
*/
static void DispatchClickEvent(nsITreeBoxObject *aTreeBoxObj,
int32_t aRowIndex, nsITreeColumn *aColumn,
const nsAString& aPseudoElt = EmptyString());
/**
* Send mouse event to the given element.
*
* @param aMessage [in] an event message (see EventForwards.h)
* @param aX [in] x coordinate in dev pixels
* @param aY [in] y coordinate in dev pixels
* @param aContent [in] the element
* @param aFrame [in] frame of the element
* @param aPresShell [in] the presshell for the element
* @param aRootWidget [in] the root widget of the element
*/
static void DispatchMouseEvent(mozilla::EventMessage aMessage,
int32_t aX, int32_t aY,
nsIContent *aContent, nsIFrame *aFrame,
nsIPresShell *aPresShell, nsIWidget *aRootWidget);
/**
* Send a touch event with a single touch point to the given element.
*
* @param aMessage [in] an event message (see EventForwards.h)
* @param aX [in] x coordinate in dev pixels
* @param aY [in] y coordinate in dev pixels
* @param aContent [in] the element
* @param aFrame [in] frame of the element
* @param aPresShell [in] the presshell for the element
* @param aRootWidget [in] the root widget of the element
*/
static void DispatchTouchEvent(mozilla::EventMessage aMessage,
int32_t aX, int32_t aY,
nsIContent* aContent, nsIFrame* aFrame,
nsIPresShell* aPresShell, nsIWidget* aRootWidget);
/**
* Return an accesskey registered on the given element by
* EventStateManager or 0 if there is no registered accesskey.
*
* @param aContent - the given element.
*/
static uint32_t GetAccessKeyFor(nsIContent *aContent);
/**
* Return DOM element related with the given node, i.e.
* a) itself if it is DOM element
* b) parent element if it is text node
* c) otherwise nullptr
*
* @param aNode [in] the given DOM node
*/
static nsIContent* GetDOMElementFor(nsIContent *aContent);
/**
* Return DOM node for the given DOM point.
*/
static nsINode *GetDOMNodeFromDOMPoint(nsINode *aNode, uint32_t aOffset);
/**
* Is the first passed in node an ancestor of the second?
* Note: A node is not considered to be the ancestor of itself.
*
* @param aPossibleAncestorNode [in] node to test for ancestor-ness of
* aPossibleDescendantNode
* @param aPossibleDescendantNode [in] node to test for descendant-ness of
* aPossibleAncestorNode
* @param aRootNode [in, optional] the root node that search
* search should be performed within
* @return true if aPossibleAncestorNode is an ancestor of
* aPossibleDescendantNode
*/
static bool IsAncestorOf(nsINode *aPossibleAncestorNode,
nsINode *aPossibleDescendantNode,
nsINode *aRootNode = nullptr);
/**
* Helper method to scroll range into view, used for implementation of
* nsIAccessibleText::scrollSubstringTo().
*
* @param aFrame the frame for accessible the range belongs to.
* @param aRange the range to scroll to
* @param aScrollType the place a range should be scrolled to
*/
static nsresult ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
uint32_t aScrollType);
/** Helper method to scroll range into view, used for implementation of
* nsIAccessibleText::scrollSubstringTo[Point]().
*
* @param aFrame the frame for accessible the range belongs to.
* @param aRange the range to scroll to
* @param aVertical how to align vertically, specified in percents, and when.
* @param aHorizontal how to align horizontally, specified in percents, and when.
*/
static nsresult ScrollSubstringTo(nsIFrame* aFrame, nsRange* aRange,
nsIPresShell::ScrollAxis aVertical,
nsIPresShell::ScrollAxis aHorizontal);
/**
* Scrolls the given frame to the point, used for implememntation of
* nsIAccessible::scrollToPoint and nsIAccessibleText::scrollSubstringToPoint.
*
* @param aScrollableFrame the scrollable frame
* @param aFrame the frame to scroll
* @param aPoint the point scroll to
*/
static void ScrollFrameToPoint(nsIFrame *aScrollableFrame,
nsIFrame *aFrame, const nsIntPoint& aPoint);
/**
* Converts scroll type constant defined in nsIAccessibleScrollType to
* vertical and horizontal parameters.
*/
static void ConvertScrollTypeToPercents(uint32_t aScrollType,
nsIPresShell::ScrollAxis *aVertical,
nsIPresShell::ScrollAxis *aHorizontal);
/**
* Returns coordinates in device pixels relative screen for the top level
* window.
*
* @param aNode the DOM node hosted in the window.
*/
static nsIntPoint GetScreenCoordsForWindow(nsINode *aNode);
/**
* Return document shell for the given DOM node.
*/
static already_AddRefed<nsIDocShell> GetDocShellFor(nsINode *aNode);
/**
* Return true if the given document is root document.
*/
static bool IsRootDocument(nsIDocument *aDocument);
/**
* Return true if the given document is content document (not chrome).
*/
static bool IsContentDocument(nsIDocument *aDocument);
/**
* Return true if the given document node is for tab document accessible.
*/
static bool IsTabDocument(nsIDocument* aDocumentNode);
/**
* Return true if the given document is an error page.
*/
static bool IsErrorPage(nsIDocument *aDocument);
/**
* Return presShell for the document containing the given DOM node.
*/
static nsIPresShell *GetPresShellFor(nsINode *aNode)
{
return aNode->OwnerDoc()->GetShell();
}
/**
* Get the ID for an element, in some types of XML this may not be the ID attribute
* @param aContent Node to get the ID for
* @param aID Where to put ID string
* @return true if there is an ID set for this node
*/
static bool GetID(nsIContent *aContent, nsAString& aID);
/**
* Convert attribute value of the given node to positive integer. If no
* attribute or wrong value then false is returned.
*/
static bool GetUIntAttr(nsIContent *aContent, nsIAtom *aAttr,
int32_t* aUInt);
/**
* Returns language for the given node.
*
* @param aContent [in] the given node
* @param aRootContent [in] container of the given node
* @param aLanguage [out] language
*/
static void GetLanguageFor(nsIContent *aContent, nsIContent *aRootContent,
nsAString& aLanguage);
/**
* Return box object for XUL treechildren element by tree box object.
*/
static already_AddRefed<nsIBoxObject>
GetTreeBodyBoxObject(nsITreeBoxObject *aTreeBoxObj);
/**
* Return tree box object from any levels DOMNode under the XUL tree.
*/
static already_AddRefed<nsITreeBoxObject>
GetTreeBoxObject(nsIContent* aContent);
/**
* Return first sensible column for the given tree box object.
*/
static already_AddRefed<nsITreeColumn>
GetFirstSensibleColumn(nsITreeBoxObject *aTree);
/**
* Return sensible columns count for the given tree box object.
*/
static uint32_t GetSensibleColumnCount(nsITreeBoxObject *aTree);
/**
* Return sensible column at the given index for the given tree box object.
*/
static already_AddRefed<nsITreeColumn>
GetSensibleColumnAt(nsITreeBoxObject *aTree, uint32_t aIndex);
/**
* Return next sensible column for the given column.
*/
static already_AddRefed<nsITreeColumn>
GetNextSensibleColumn(nsITreeColumn *aColumn);
/**
* Return previous sensible column for the given column.
*/
static already_AddRefed<nsITreeColumn>
GetPreviousSensibleColumn(nsITreeColumn *aColumn);
/**
* Return true if the given column is hidden (i.e. not sensible).
*/
static bool IsColumnHidden(nsITreeColumn *aColumn);
/**
* Scroll content into view.
*/
static void ScrollTo(nsIPresShell* aPresShell, nsIContent* aContent,
uint32_t aScrollType);
/**
* Return true if the given node is table header element.
*/
static bool IsHTMLTableHeader(nsIContent *aContent)
{
return aContent->NodeInfo()->Equals(nsGkAtoms::th) ||
aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::scope);
}
/**
* Returns true if the given string is empty or contains whitespace symbols
* only. In contrast to nsWhitespaceTokenizer class it takes into account
* non-breaking space (0xa0).
*/
static bool IsWhitespaceString(const nsSubstring& aString);
/**
* Returns true if the given character is whitespace symbol.
*/
static bool IsWhitespace(char16_t aChar)
{
return aChar == ' ' || aChar == '\n' ||
aChar == '\r' || aChar == '\t' || aChar == 0xa0;
}
/*
* Return true if there are any observers of accessible events.
*/
static bool AccEventObserversExist();
/**
* Notify accessible event observers of an event.
*/
static void DispatchAccEvent(RefPtr<nsIAccessibleEvent> aEvent);
/**
* Return a role attribute on XBL bindings of the element.
*/
static void XBLBindingRole(const nsIContent* aEl, nsAString& aRole);
};
#endif

View file

@ -0,0 +1,79 @@
/* -*- 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 "nsEventShell.h"
#include "nsAccUtils.h"
#include "mozilla/StaticPtr.h"
using namespace mozilla;
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// nsEventShell
////////////////////////////////////////////////////////////////////////////////
void
nsEventShell::FireEvent(AccEvent* aEvent)
{
if (!aEvent || aEvent->mEventRule == AccEvent::eDoNotEmit)
return;
Accessible* accessible = aEvent->GetAccessible();
NS_ENSURE_TRUE_VOID(accessible);
nsINode* node = accessible->GetNode();
if (node) {
sEventTargetNode = node;
sEventFromUserInput = aEvent->IsFromUserInput();
}
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eEvents)) {
logging::MsgBegin("EVENTS", "events fired");
nsAutoString type;
GetAccService()->GetStringEventType(aEvent->GetEventType(), type);
logging::MsgEntry("type: %s", NS_ConvertUTF16toUTF8(type).get());
logging::AccessibleInfo("target", aEvent->GetAccessible());
logging::MsgEnd();
}
#endif
accessible->HandleAccEvent(aEvent);
aEvent->mEventRule = AccEvent::eDoNotEmit;
sEventTargetNode = nullptr;
}
void
nsEventShell::FireEvent(uint32_t aEventType, Accessible* aAccessible,
EIsFromUserInput aIsFromUserInput)
{
NS_ENSURE_TRUE_VOID(aAccessible);
RefPtr<AccEvent> event = new AccEvent(aEventType, aAccessible,
aIsFromUserInput);
FireEvent(event);
}
void
nsEventShell::GetEventAttributes(nsINode *aNode,
nsIPersistentProperties *aAttributes)
{
if (aNode != sEventTargetNode)
return;
nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::eventFromInput,
sEventFromUserInput ? NS_LITERAL_STRING("true") :
NS_LITERAL_STRING("false"));
}
////////////////////////////////////////////////////////////////////////////////
// nsEventShell: private
bool nsEventShell::sEventFromUserInput = false;
StaticRefPtr<nsINode> nsEventShell::sEventTargetNode;

View file

@ -0,0 +1,67 @@
/* -*- 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/. */
#ifndef _nsEventShell_H_
#define _nsEventShell_H_
#include "AccEvent.h"
namespace mozilla {
template<typename T> class StaticRefPtr;
}
class nsIPersistentProperties;
/**
* Used for everything about events.
*/
class nsEventShell
{
public:
/**
* Fire the accessible event.
*/
static void FireEvent(mozilla::a11y::AccEvent* aEvent);
/**
* Fire accessible event of the given type for the given accessible.
*
* @param aEventType [in] the event type
* @param aAccessible [in] the event target
*/
static void FireEvent(uint32_t aEventType,
mozilla::a11y::Accessible* aAccessible,
mozilla::a11y::EIsFromUserInput aIsFromUserInput = mozilla::a11y::eAutoDetect);
/**
* Fire state change event.
*/
static void FireEvent(mozilla::a11y::Accessible* aTarget, uint64_t aState,
bool aIsEnabled, bool aIsFromUserInput)
{
RefPtr<mozilla::a11y::AccStateChangeEvent> stateChangeEvent =
new mozilla::a11y::AccStateChangeEvent(aTarget, aState, aIsEnabled,
(aIsFromUserInput ?
mozilla::a11y::eFromUserInput :
mozilla::a11y::eNoUserInput));
FireEvent(stateChangeEvent);
}
/**
* Append 'event-from-input' object attribute if the accessible event has
* been fired just now for the given node.
*
* @param aNode [in] the DOM node
* @param aAttributes [in, out] the attributes
*/
static void GetEventAttributes(nsINode *aNode,
nsIPersistentProperties *aAttributes);
private:
static mozilla::StaticRefPtr<nsINode> sEventTargetNode;
static bool sEventFromUserInput;
};
#endif

View file

@ -0,0 +1,364 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=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 "nsTextEquivUtils.h"
#include "Accessible-inl.h"
#include "AccIterator.h"
#include "nsCoreUtils.h"
#include "nsIDOMXULLabeledControlEl.h"
using namespace mozilla::a11y;
/**
* The accessible for which we are computing a text equivalent. It is useful
* for bailing out during recursive text computation, or for special cases
* like step f. of the ARIA implementation guide.
*/
static Accessible* sInitiatorAcc = nullptr;
////////////////////////////////////////////////////////////////////////////////
// nsTextEquivUtils. Public.
nsresult
nsTextEquivUtils::GetNameFromSubtree(Accessible* aAccessible,
nsAString& aName)
{
aName.Truncate();
if (sInitiatorAcc)
return NS_OK;
sInitiatorAcc = aAccessible;
if (GetRoleRule(aAccessible->Role()) == eNameFromSubtreeRule) {
//XXX: is it necessary to care the accessible is not a document?
if (aAccessible->IsContent()) {
nsAutoString name;
AppendFromAccessibleChildren(aAccessible, &name);
name.CompressWhitespace();
if (!nsCoreUtils::IsWhitespaceString(name))
aName = name;
}
}
sInitiatorAcc = nullptr;
return NS_OK;
}
nsresult
nsTextEquivUtils::GetTextEquivFromIDRefs(Accessible* aAccessible,
nsIAtom *aIDRefsAttr,
nsAString& aTextEquiv)
{
aTextEquiv.Truncate();
nsIContent* content = aAccessible->GetContent();
if (!content)
return NS_OK;
nsIContent* refContent = nullptr;
IDRefsIterator iter(aAccessible->Document(), content, aIDRefsAttr);
while ((refContent = iter.NextElem())) {
if (!aTextEquiv.IsEmpty())
aTextEquiv += ' ';
nsresult rv = AppendTextEquivFromContent(aAccessible, refContent,
&aTextEquiv);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsTextEquivUtils::AppendTextEquivFromContent(Accessible* aInitiatorAcc,
nsIContent *aContent,
nsAString *aString)
{
// Prevent recursion which can cause infinite loops.
if (sInitiatorAcc)
return NS_OK;
sInitiatorAcc = aInitiatorAcc;
// If the given content is not visible or isn't accessible then go down
// through the DOM subtree otherwise go down through accessible subtree and
// calculate the flat string.
nsIFrame *frame = aContent->GetPrimaryFrame();
bool isVisible = frame && frame->StyleVisibility()->IsVisible();
nsresult rv = NS_ERROR_FAILURE;
bool goThroughDOMSubtree = true;
if (isVisible) {
Accessible* accessible =
sInitiatorAcc->Document()->GetAccessible(aContent);
if (accessible) {
rv = AppendFromAccessible(accessible, aString);
goThroughDOMSubtree = false;
}
}
if (goThroughDOMSubtree)
rv = AppendFromDOMNode(aContent, aString);
sInitiatorAcc = nullptr;
return rv;
}
nsresult
nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent,
nsAString *aString)
{
if (aContent->IsNodeOfType(nsINode::eTEXT)) {
bool isHTMLBlock = false;
nsIContent *parentContent = aContent->GetFlattenedTreeParent();
if (parentContent) {
nsIFrame *frame = parentContent->GetPrimaryFrame();
if (frame) {
// If this text is inside a block level frame (as opposed to span
// level), we need to add spaces around that block's text, so we don't
// get words jammed together in final name.
const nsStyleDisplay* display = frame->StyleDisplay();
if (display->IsBlockOutsideStyle() ||
display->mDisplay == StyleDisplay::TableCell) {
isHTMLBlock = true;
if (!aString->IsEmpty()) {
aString->Append(char16_t(' '));
}
}
}
}
if (aContent->TextLength() > 0) {
nsIFrame *frame = aContent->GetPrimaryFrame();
if (frame) {
nsIFrame::RenderedText text = frame->GetRenderedText(0,
UINT32_MAX, nsIFrame::TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
nsIFrame::TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
aString->Append(text.mString);
} else {
// If aContent is an object that is display: none, we have no a frame.
aContent->AppendTextTo(*aString);
}
if (isHTMLBlock && !aString->IsEmpty()) {
aString->Append(char16_t(' '));
}
}
return NS_OK;
}
if (aContent->IsHTMLElement() &&
aContent->NodeInfo()->Equals(nsGkAtoms::br)) {
aString->AppendLiteral("\r\n");
return NS_OK;
}
return NS_OK_NO_NAME_CLAUSE_HANDLED;
}
////////////////////////////////////////////////////////////////////////////////
// nsTextEquivUtils. Private.
nsresult
nsTextEquivUtils::AppendFromAccessibleChildren(Accessible* aAccessible,
nsAString *aString)
{
nsresult rv = NS_OK_NO_NAME_CLAUSE_HANDLED;
uint32_t childCount = aAccessible->ChildCount();
for (uint32_t childIdx = 0; childIdx < childCount; childIdx++) {
Accessible* child = aAccessible->GetChildAt(childIdx);
rv = AppendFromAccessible(child, aString);
NS_ENSURE_SUCCESS(rv, rv);
}
return rv;
}
nsresult
nsTextEquivUtils::AppendFromAccessible(Accessible* aAccessible,
nsAString *aString)
{
//XXX: is it necessary to care the accessible is not a document?
if (aAccessible->IsContent()) {
nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(),
aString);
if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
return rv;
}
bool isEmptyTextEquiv = true;
// If the name is from tooltip then append it to result string in the end
// (see h. step of name computation guide).
nsAutoString text;
if (aAccessible->Name(text) != eNameFromTooltip)
isEmptyTextEquiv = !AppendString(aString, text);
// Implementation of f. step.
nsresult rv = AppendFromValue(aAccessible, aString);
NS_ENSURE_SUCCESS(rv, rv);
if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
isEmptyTextEquiv = false;
// Implementation of g) step of text equivalent computation guide. Go down
// into subtree if accessible allows "text equivalent from subtree rule" or
// it's not root and not control.
if (isEmptyTextEquiv) {
uint32_t nameRule = GetRoleRule(aAccessible->Role());
if (nameRule & eNameFromSubtreeIfReqRule) {
rv = AppendFromAccessibleChildren(aAccessible, aString);
NS_ENSURE_SUCCESS(rv, rv);
if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
isEmptyTextEquiv = false;
}
}
// Implementation of h. step
if (isEmptyTextEquiv && !text.IsEmpty()) {
AppendString(aString, text);
return NS_OK;
}
return rv;
}
nsresult
nsTextEquivUtils::AppendFromValue(Accessible* aAccessible,
nsAString *aString)
{
if (GetRoleRule(aAccessible->Role()) != eNameFromValueRule)
return NS_OK_NO_NAME_CLAUSE_HANDLED;
// Implementation of step f. of text equivalent computation. If the given
// accessible is not root accessible (the accessible the text equivalent is
// computed for in the end) then append accessible value. Otherwise append
// value if and only if the given accessible is in the middle of its parent.
nsAutoString text;
if (aAccessible != sInitiatorAcc) {
aAccessible->Value(text);
return AppendString(aString, text) ?
NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
}
//XXX: is it necessary to care the accessible is not a document?
if (aAccessible->IsDoc())
return NS_ERROR_UNEXPECTED;
nsIContent *content = aAccessible->GetContent();
for (nsIContent* childContent = content->GetPreviousSibling(); childContent;
childContent = childContent->GetPreviousSibling()) {
// check for preceding text...
if (!childContent->TextIsOnlyWhitespace()) {
for (nsIContent* siblingContent = content->GetNextSibling(); siblingContent;
siblingContent = siblingContent->GetNextSibling()) {
// .. and subsequent text
if (!siblingContent->TextIsOnlyWhitespace()) {
aAccessible->Value(text);
return AppendString(aString, text) ?
NS_OK : NS_OK_NO_NAME_CLAUSE_HANDLED;
break;
}
}
break;
}
}
return NS_OK_NO_NAME_CLAUSE_HANDLED;
}
nsresult
nsTextEquivUtils::AppendFromDOMChildren(nsIContent *aContent,
nsAString *aString)
{
for (nsIContent* childContent = aContent->GetFirstChild(); childContent;
childContent = childContent->GetNextSibling()) {
nsresult rv = AppendFromDOMNode(childContent, aString);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
nsTextEquivUtils::AppendFromDOMNode(nsIContent *aContent, nsAString *aString)
{
nsresult rv = AppendTextEquivFromTextContent(aContent, aString);
NS_ENSURE_SUCCESS(rv, rv);
if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED)
return NS_OK;
if (aContent->IsXULElement()) {
nsAutoString textEquivalent;
nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
do_QueryInterface(aContent);
if (labeledEl) {
labeledEl->GetLabel(textEquivalent);
} else {
if (aContent->NodeInfo()->Equals(nsGkAtoms::label,
kNameSpaceID_XUL))
aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value,
textEquivalent);
if (textEquivalent.IsEmpty())
aContent->GetAttr(kNameSpaceID_None,
nsGkAtoms::tooltiptext, textEquivalent);
}
AppendString(aString, textEquivalent);
}
return AppendFromDOMChildren(aContent, aString);
}
bool
nsTextEquivUtils::AppendString(nsAString *aString,
const nsAString& aTextEquivalent)
{
if (aTextEquivalent.IsEmpty())
return false;
// Insert spaces to insure that words from controls aren't jammed together.
if (!aString->IsEmpty() && !nsCoreUtils::IsWhitespace(aString->Last()))
aString->Append(char16_t(' '));
aString->Append(aTextEquivalent);
if (!nsCoreUtils::IsWhitespace(aString->Last()))
aString->Append(char16_t(' '));
return true;
}
uint32_t
nsTextEquivUtils::GetRoleRule(role aRole)
{
#define ROLE(geckoRole, stringRole, atkRole, \
macRole, msaaRole, ia2Role, nameRule) \
case roles::geckoRole: \
return nameRule;
switch (aRole) {
#include "RoleMap.h"
default:
MOZ_CRASH("Unknown role.");
}
#undef ROLE
}

View file

@ -0,0 +1,162 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:expandtab:shiftwidth=2:tabstop=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/. */
#ifndef _nsTextEquivUtils_H_
#define _nsTextEquivUtils_H_
#include "Accessible.h"
#include "Role.h"
class nsIContent;
/**
* Text equivalent computation rules (see nsTextEquivUtils::gRoleToNameRulesMap)
*/
enum ETextEquivRule
{
// No rule.
eNoNameRule = 0x00,
// Walk into subtree only if the currently navigated accessible is not root
// accessible (i.e. if the accessible is part of text equivalent computation).
eNameFromSubtreeIfReqRule = 0x01,
// Text equivalent computation from subtree is allowed.
eNameFromSubtreeRule = 0x03,
// The accessible allows to append its value to text equivalent.
// XXX: This is temporary solution. Once we move accessible value of links
// and linkable accessibles to MSAA part we can remove this.
eNameFromValueRule = 0x04
};
/**
* The class provides utils methods to compute the accessible name and
* description.
*/
class nsTextEquivUtils
{
public:
typedef mozilla::a11y::Accessible Accessible;
/**
* Determines if the accessible has a given name rule.
*
* @param aAccessible [in] the given accessible
* @param aRule [in] a given name rule
* @return true if the accessible has the rule
*/
static inline bool HasNameRule(Accessible* aAccessible, ETextEquivRule aRule)
{
return (GetRoleRule(aAccessible->Role()) & aRule) == aRule;
}
/**
* Calculates the name from accessible subtree if allowed.
*
* @param aAccessible [in] the given accessible
* @param aName [out] accessible name
*/
static nsresult GetNameFromSubtree(Accessible* aAccessible,
nsAString& aName);
/**
* Calculates text equivalent from the subtree. Similar to GetNameFromSubtree.
* However it returns not empty result for things like HTML p.
*/
static void GetTextEquivFromSubtree(Accessible* aAccessible,
nsString& aTextEquiv)
{
aTextEquiv.Truncate();
AppendFromAccessibleChildren(aAccessible, &aTextEquiv);
aTextEquiv.CompressWhitespace();
}
/**
* Calculates text equivalent for the given accessible from its IDRefs
* attribute (like aria-labelledby or aria-describedby).
*
* @param aAccessible [in] the accessible text equivalent is computed for
* @param aIDRefsAttr [in] IDRefs attribute on DOM node of the accessible
* @param aTextEquiv [out] result text equivalent
*/
static nsresult GetTextEquivFromIDRefs(Accessible* aAccessible,
nsIAtom *aIDRefsAttr,
nsAString& aTextEquiv);
/**
* Calculates the text equivalent from the given content and its subtree if
* allowed and appends it to the given string.
*
* @param aInitiatorAcc [in] the accessible text equivalent is computed for
* in the end (root accessible of text equivalent
* calculation recursion)
* @param aContent [in] the given content the text equivalent is
* computed from
* @param aString [in, out] the string
*/
static nsresult AppendTextEquivFromContent(Accessible* aInitiatorAcc,
nsIContent *aContent,
nsAString *aString);
/**
* Calculates the text equivalent from the given text content (may be text
* node or html:br) and appends it to the given string.
*
* @param aContent [in] the text content
* @param aString [in, out] the string
*/
static nsresult AppendTextEquivFromTextContent(nsIContent *aContent,
nsAString *aString);
private:
/**
* Iterates accessible children and calculates text equivalent from each
* child.
*/
static nsresult AppendFromAccessibleChildren(Accessible* aAccessible,
nsAString *aString);
/**
* Calculates text equivalent from the given accessible and its subtree if
* allowed.
*/
static nsresult AppendFromAccessible(Accessible* aAccessible,
nsAString *aString);
/**
* Calculates text equivalent from the value of given accessible.
*/
static nsresult AppendFromValue(Accessible* aAccessible,
nsAString *aString);
/**
* Iterates DOM children and calculates text equivalent from each child node.
*/
static nsresult AppendFromDOMChildren(nsIContent *aContent,
nsAString *aString);
/**
* Calculates text equivalent from the given DOM node and its subtree if
* allowed.
*/
static nsresult AppendFromDOMNode(nsIContent *aContent, nsAString *aString);
/**
* Concatenates strings and appends space between them. Returns true if
* text equivalent string was appended.
*/
static bool AppendString(nsAString *aString,
const nsAString& aTextEquivalent);
/**
* Returns the rule (constant of ETextEquivRule) for a given role.
*/
static uint32_t GetRoleRule(mozilla::a11y::roles::Role aRole);
};
#endif