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

17
accessible/.eslintrc.js Normal file
View file

@ -0,0 +1,17 @@
"use strict";
module.exports = {
"extends": [
"../.eslintrc.js"
],
"globals": {
"Cc": true,
"Ci": true,
"Components": true,
"console": true,
"Cu": true,
"dump": true,
"Services": true,
"XPCOMUtils": true
}
};

View file

@ -0,0 +1,68 @@
/* -*- 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 "AccessibleNode.h"
#include "mozilla/dom/AccessibleNodeBinding.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "Accessible-inl.h"
#include "nsAccessibilityService.h"
#include "DocAccessible.h"
using namespace mozilla;
using namespace mozilla::a11y;
using namespace mozilla::dom;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(AccessibleNode)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AccessibleNode)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(AccessibleNode)
NS_IMPL_CYCLE_COLLECTING_RELEASE(AccessibleNode)
AccessibleNode::AccessibleNode(nsINode* aNode) : mDOMNode(aNode)
{
DocAccessible* doc =
GetOrCreateAccService()->GetDocAccessible(mDOMNode->OwnerDoc());
if (doc) {
mIntl = doc->GetAccessible(mDOMNode);
}
}
AccessibleNode::~AccessibleNode()
{
}
/* virtual */ JSObject*
AccessibleNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return AccessibleNodeBinding::Wrap(aCx, this, aGivenProto);
}
/* virtual */ ParentObject
AccessibleNode::GetParentObject() const
{
return mDOMNode->GetParentObject();
}
void
AccessibleNode::GetRole(nsAString& aRole)
{
if (mIntl) {
GetOrCreateAccService()->GetStringRole(mIntl->Role(), aRole);
return;
}
aRole.AssignLiteral("unknown");
}
nsINode*
AccessibleNode::GetDOMNode()
{
return mDOMNode;
}

View file

@ -0,0 +1,53 @@
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
/* vim: set ts=2 et sw=2 tw=40: */
/* 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_AOM_ACCESSIBLENODE_H
#define A11Y_AOM_ACCESSIBLENODE_H
#include "nsWrapperCache.h"
#include "mozilla/RefPtr.h"
class nsINode;
namespace mozilla {
namespace a11y {
class Accessible;
}
namespace dom {
struct ParentObject;
class AccessibleNode : public nsISupports,
public nsWrapperCache
{
public:
explicit AccessibleNode(nsINode* aNode);
NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AccessibleNode);
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override final;
virtual dom::ParentObject GetParentObject() const final;
void GetRole(nsAString& aRole);
nsINode* GetDOMNode();
protected:
AccessibleNode(const AccessibleNode& aCopy) = delete;
AccessibleNode& operator=(const AccessibleNode& aCopy) = delete;
virtual ~AccessibleNode();
RefPtr<a11y::Accessible> mIntl;
RefPtr<nsINode> mDOMNode;
};
} // dom
} // mozilla
#endif // A11Y_JSAPI_ACCESSIBLENODE

40
accessible/aom/moz.build Normal file
View file

@ -0,0 +1,40 @@
# -*- 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.mozilla.dom += [
'AccessibleNode.h',
]
UNIFIED_SOURCES += [
'AccessibleNode.cpp',
]
LOCAL_INCLUDES += [
'/accessible/base',
'/accessible/generic',
]
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
LOCAL_INCLUDES += [
'/accessible/atk',
]
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',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

View file

@ -0,0 +1,21 @@
/* -*- 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_ARIAGRIDACCESSIBLEWRAP_H
#define MOZILLA_A11Y_ARIAGRIDACCESSIBLEWRAP_H
#include "ARIAGridAccessible.h"
namespace mozilla {
namespace a11y {
typedef class ARIAGridAccessible ARIAGridAccessibleWrap;
typedef class ARIAGridCellAccessible ARIAGridCellAccessibleWrap;
} // namespace a11y
} // namespace mozilla
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,92 @@
/* -*- 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 __NS_ACCESSIBLE_WRAP_H__
#define __NS_ACCESSIBLE_WRAP_H__
#include "nsCOMPtr.h"
#include "Accessible.h"
struct _AtkObject;
typedef struct _AtkObject AtkObject;
enum AtkProperty {
PROP_0, // gobject convention
PROP_NAME,
PROP_DESCRIPTION,
PROP_PARENT, // ancestry has changed
PROP_ROLE,
PROP_LAYER,
PROP_MDI_ZORDER,
PROP_TABLE_CAPTION,
PROP_TABLE_COLUMN_DESCRIPTION,
PROP_TABLE_COLUMN_HEADER,
PROP_TABLE_ROW_DESCRIPTION,
PROP_TABLE_ROW_HEADER,
PROP_TABLE_SUMMARY,
PROP_LAST // gobject convention
};
struct AtkPropertyChange {
int32_t type; // property type as listed above
void *oldvalue;
void *newvalue;
};
namespace mozilla {
namespace a11y {
class MaiHyperlink;
/**
* AccessibleWrap, and its descendents in atk directory provide the
* implementation of AtkObject.
*/
class AccessibleWrap : public Accessible
{
public:
AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
virtual ~AccessibleWrap();
void ShutdownAtkObject();
virtual void Shutdown() override;
// return the atk object for this AccessibleWrap
virtual void GetNativeInterface(void** aOutAccessible) override;
virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
AtkObject * GetAtkObject(void);
static AtkObject* GetAtkObject(Accessible* aAccessible);
bool IsValidObject();
static const char * ReturnString(nsAString &aString) {
static nsCString returnedString;
returnedString = NS_ConvertUTF16toUTF8(aString);
return returnedString.get();
}
static void GetKeyBinding(Accessible* aAccessible, nsAString& aResult);
static Accessible* GetColumnHeader(TableAccessible* aAccessible,
int32_t aColIdx);
static Accessible* GetRowHeader(TableAccessible* aAccessible,
int32_t aRowIdx);
protected:
nsresult FireAtkStateChangeEvent(AccEvent* aEvent, AtkObject *aObject);
nsresult FireAtkTextChangedEvent(AccEvent* aEvent, AtkObject *aObject);
AtkObject *mAtkObject;
private:
uint16_t CreateMaiInterfaces();
};
} // namespace a11y
} // namespace mozilla
#endif /* __NS_ACCESSIBLE_WRAP_H__ */

View file

@ -0,0 +1,167 @@
/* -*- 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 "ApplicationAccessibleWrap.h"
#include "nsCOMPtr.h"
#include "nsMai.h"
#include "nsAccessibilityService.h"
#include <gtk/gtk.h>
#include <atk/atk.h>
using namespace mozilla;
using namespace mozilla::a11y;
// ApplicationAccessibleWrap
ApplicationAccessibleWrap::ApplicationAccessibleWrap():
ApplicationAccessible()
{
}
ApplicationAccessibleWrap::~ApplicationAccessibleWrap()
{
AccessibleWrap::ShutdownAtkObject();
}
gboolean
toplevel_event_watcher(GSignalInvocationHint* ihint,
guint n_param_values,
const GValue* param_values,
gpointer data)
{
static GQuark sQuark_gecko_acc_obj = 0;
if (!sQuark_gecko_acc_obj)
sQuark_gecko_acc_obj = g_quark_from_static_string("GeckoAccObj");
if (nsAccessibilityService::IsShutdown())
return TRUE;
GObject* object = reinterpret_cast<GObject*>(g_value_get_object(param_values));
if (!GTK_IS_WINDOW(object))
return TRUE;
AtkObject* child = gtk_widget_get_accessible(GTK_WIDGET(object));
// GTK native dialog
if (!IS_MAI_OBJECT(child) &&
(atk_object_get_role(child) == ATK_ROLE_DIALOG)) {
if (data == reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_SHOW)) {
// Attach the dialog accessible to app accessible tree
Accessible* windowAcc = GetAccService()->AddNativeRootAccessible(child);
g_object_set_qdata(G_OBJECT(child), sQuark_gecko_acc_obj,
reinterpret_cast<gpointer>(windowAcc));
} else {
// Deattach the dialog accessible
Accessible* windowAcc =
reinterpret_cast<Accessible*>
(g_object_get_qdata(G_OBJECT(child), sQuark_gecko_acc_obj));
if (windowAcc) {
GetAccService()->RemoveNativeRootAccessible(windowAcc);
g_object_set_qdata(G_OBJECT(child), sQuark_gecko_acc_obj, nullptr);
}
}
}
return TRUE;
}
ENameValueFlag
ApplicationAccessibleWrap::Name(nsString& aName)
{
// ATK doesn't provide a way to obtain an application name (for example,
// Firefox or Thunderbird) like IA2 does. Thus let's return an application
// name as accessible name that was used to get a branding name (for example,
// Minefield aka nightly Firefox or Daily aka nightly Thunderbird).
AppName(aName);
return eNameOK;
}
void
ApplicationAccessibleWrap::GetNativeInterface(void** aOutAccessible)
{
*aOutAccessible = nullptr;
if (!mAtkObject) {
mAtkObject =
reinterpret_cast<AtkObject*>(g_object_new(MAI_TYPE_ATK_OBJECT, nullptr));
if (!mAtkObject)
return;
atk_object_initialize(mAtkObject, this);
mAtkObject->role = ATK_ROLE_INVALID;
mAtkObject->layer = ATK_LAYER_INVALID;
}
*aOutAccessible = mAtkObject;
}
struct AtkRootAccessibleAddedEvent {
AtkObject *app_accessible;
AtkObject *root_accessible;
uint32_t index;
};
gboolean fireRootAccessibleAddedCB(gpointer data)
{
AtkRootAccessibleAddedEvent* eventData = (AtkRootAccessibleAddedEvent*)data;
g_signal_emit_by_name(eventData->app_accessible, "children_changed::add",
eventData->index, eventData->root_accessible, nullptr);
g_object_unref(eventData->app_accessible);
g_object_unref(eventData->root_accessible);
free(data);
return FALSE;
}
bool
ApplicationAccessibleWrap::InsertChildAt(uint32_t aIdx, Accessible* aChild)
{
if (!ApplicationAccessible::InsertChildAt(aIdx, aChild))
return false;
AtkObject* atkAccessible = AccessibleWrap::GetAtkObject(aChild);
atk_object_set_parent(atkAccessible, mAtkObject);
uint32_t count = mChildren.Length();
// Emit children_changed::add in a timeout
// to make sure aRootAccWrap is fully initialized.
AtkRootAccessibleAddedEvent* eventData = (AtkRootAccessibleAddedEvent*)
malloc(sizeof(AtkRootAccessibleAddedEvent));
if (eventData) {
eventData->app_accessible = mAtkObject;
eventData->root_accessible = atkAccessible;
eventData->index = count -1;
g_object_ref(mAtkObject);
g_object_ref(atkAccessible);
g_timeout_add(0, fireRootAccessibleAddedCB, eventData);
}
return true;
}
bool
ApplicationAccessibleWrap::RemoveChild(Accessible* aChild)
{
int32_t index = aChild->IndexInParent();
AtkObject* atkAccessible = AccessibleWrap::GetAtkObject(aChild);
atk_object_set_parent(atkAccessible, nullptr);
g_signal_emit_by_name(mAtkObject, "children_changed::remove", index,
atkAccessible, nullptr);
return ApplicationAccessible::RemoveChild(aChild);
}

View file

@ -0,0 +1,35 @@
/* -*- 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_ApplicationAccessibleWrap_h__
#define mozilla_a11y_ApplicationAccessibleWrap_h__
#include "ApplicationAccessible.h"
namespace mozilla {
namespace a11y {
class ApplicationAccessibleWrap: public ApplicationAccessible
{
public:
ApplicationAccessibleWrap();
virtual ~ApplicationAccessibleWrap();
// Accessible
virtual mozilla::a11y::ENameValueFlag Name(nsString& aName) override;
virtual bool InsertChildAt(uint32_t aIdx, Accessible* aChild) override;
virtual bool RemoveChild(Accessible* aChild) override;
/**
* Return the atk object for app root accessible.
*/
virtual void GetNativeInterface(void** aOutAccessible) override;
};
} // namespace a11y
} // namespace mozilla
#endif /* __NS_APP_ROOT_ACCESSIBLE_H__ */

View file

@ -0,0 +1,126 @@
/* -*- 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 <atk/atk.h>
#include "AtkSocketAccessible.h"
#include "InterfaceInitFuncs.h"
#include "nsMai.h"
#include "mozilla/Likely.h"
using namespace mozilla::a11y;
AtkSocketEmbedType AtkSocketAccessible::g_atk_socket_embed = nullptr;
GType AtkSocketAccessible::g_atk_socket_type = G_TYPE_INVALID;
const char* AtkSocketAccessible::sATKSocketEmbedSymbol = "atk_socket_embed";
const char* AtkSocketAccessible::sATKSocketGetTypeSymbol = "atk_socket_get_type";
bool AtkSocketAccessible::gCanEmbed = FALSE;
extern "C" void mai_atk_component_iface_init(AtkComponentIface* aIface);
G_DEFINE_TYPE_EXTENDED(MaiAtkSocket, mai_atk_socket,
AtkSocketAccessible::g_atk_socket_type, 0,
G_IMPLEMENT_INTERFACE(ATK_TYPE_COMPONENT,
mai_atk_component_iface_init))
void
mai_atk_socket_class_init(MaiAtkSocketClass* aAcc)
{
}
void
mai_atk_socket_init(MaiAtkSocket* aAcc)
{
}
static AtkObject*
mai_atk_socket_new(AccessibleWrap* aAccWrap)
{
NS_ENSURE_TRUE(aAccWrap, nullptr);
MaiAtkSocket* acc = nullptr;
acc = static_cast<MaiAtkSocket*>(g_object_new(MAI_TYPE_ATK_SOCKET, nullptr));
NS_ENSURE_TRUE(acc, nullptr);
acc->accWrap = aAccWrap;
return ATK_OBJECT(acc);
}
extern "C" {
static AtkObject*
RefAccessibleAtPoint(AtkComponent* aComponent, gint aX, gint aY,
AtkCoordType aCoordType)
{
NS_ENSURE_TRUE(MAI_IS_ATK_SOCKET(aComponent), nullptr);
return refAccessibleAtPointHelper(ATK_OBJECT(MAI_ATK_SOCKET(aComponent)),
aX, aY, aCoordType);
}
static void
GetExtents(AtkComponent* aComponent, gint* aX, gint* aY, gint* aWidth,
gint* aHeight, AtkCoordType aCoordType)
{
*aX = *aY = *aWidth = *aHeight = 0;
if (!MAI_IS_ATK_SOCKET(aComponent))
return;
getExtentsHelper(ATK_OBJECT(MAI_ATK_SOCKET(aComponent)),
aX, aY, aWidth, aHeight, aCoordType);
}
}
void
mai_atk_component_iface_init(AtkComponentIface* aIface)
{
NS_ASSERTION(aIface, "Invalid Interface");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->ref_accessible_at_point = RefAccessibleAtPoint;
aIface->get_extents = GetExtents;
}
AtkSocketAccessible::AtkSocketAccessible(nsIContent* aContent,
DocAccessible* aDoc,
const nsCString& aPlugId) :
AccessibleWrap(aContent, aDoc)
{
mAtkObject = mai_atk_socket_new(this);
if (!mAtkObject)
return;
// Embeds the children of an AtkPlug, specified by plugId, as the children of
// this socket.
// Using G_TYPE macros instead of ATK_SOCKET macros to avoid undefined
// symbols.
if (gCanEmbed && G_TYPE_CHECK_INSTANCE_TYPE(mAtkObject, g_atk_socket_type) &&
!aPlugId.IsVoid()) {
AtkSocket* accSocket =
G_TYPE_CHECK_INSTANCE_CAST(mAtkObject, g_atk_socket_type, AtkSocket);
g_atk_socket_embed(accSocket, (gchar*)aPlugId.get());
}
}
void
AtkSocketAccessible::GetNativeInterface(void** aOutAccessible)
{
*aOutAccessible = mAtkObject;
}
void
AtkSocketAccessible::Shutdown()
{
if (mAtkObject) {
if (MAI_IS_ATK_SOCKET(mAtkObject))
MAI_ATK_SOCKET(mAtkObject)->accWrap = nullptr;
g_object_unref(mAtkObject);
mAtkObject = nullptr;
}
AccessibleWrap::Shutdown();
}

View file

@ -0,0 +1,56 @@
/* -*- 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 _AtkSocketAccessible_H_
#define _AtkSocketAccessible_H_
#include "AccessibleWrap.h"
// This file gets included by nsAccessibilityService.cpp, which can't include
// atk.h (or glib.h), so we can't rely on it being included.
#ifdef __ATK_H__
extern "C" typedef void (*AtkSocketEmbedType) (AtkSocket*, gchar*);
#else
extern "C" typedef void (*AtkSocketEmbedType) (void*, void*);
#endif
namespace mozilla {
namespace a11y {
/**
* Provides a AccessibleWrap wrapper around AtkSocket for out-of-process
* accessibles.
*/
class AtkSocketAccessible : public AccessibleWrap
{
public:
// Soft references to AtkSocket
static AtkSocketEmbedType g_atk_socket_embed;
#ifdef __ATK_H__
static GType g_atk_socket_type;
#endif
static const char* sATKSocketEmbedSymbol;
static const char* sATKSocketGetTypeSymbol;
/*
* True if the current Atk version supports AtkSocket and it was correctly
* loaded.
*/
static bool gCanEmbed;
AtkSocketAccessible(nsIContent* aContent, DocAccessible* aDoc,
const nsCString& aPlugId);
virtual void Shutdown() override;
virtual void GetNativeInterface(void** aOutAccessible) override;
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,25 @@
/* -*- 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 "nsMai.h"
#include "DocAccessibleWrap.h"
using namespace mozilla::a11y;
////////////////////////////////////////////////////////////////////////////////
// DocAccessibleWrap
////////////////////////////////////////////////////////////////////////////////
DocAccessibleWrap::
DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
DocAccessible(aDocument, aPresShell), mActivated(false)
{
}
DocAccessibleWrap::~DocAccessibleWrap()
{
}

View file

@ -0,0 +1,31 @@
/* -*- 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/. */
/* For documentation of the accessibility architecture,
* see http://lxr.mozilla.org/seamonkey/source/accessible/accessible-docs.html
*/
#ifndef mozilla_a11y_DocAccessibleWrap_h__
#define mozilla_a11y_DocAccessibleWrap_h__
#include "DocAccessible.h"
namespace mozilla {
namespace a11y {
class DocAccessibleWrap : public DocAccessible
{
public:
DocAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
virtual ~DocAccessibleWrap();
bool mActivated;
};
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,23 @@
/* -*- 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_HTMLTableAccessibleWrap_h__
#define mozilla_a11y_HTMLTableAccessibleWrap_h__
#include "HTMLTableAccessible.h"
namespace mozilla {
namespace a11y {
typedef class HTMLTableAccessible HTMLTableAccessibleWrap;
typedef class HTMLTableCellAccessible HTMLTableCellAccessibleWrap;
typedef class HTMLTableHeaderCellAccessible HTMLTableHeaderCellAccessibleWrap;
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,21 @@
/* -*- 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_HyperTextAccessibleWrap_h__
#define mozilla_a11y_HyperTextAccessibleWrap_h__
#include "HyperTextAccessible.h"
namespace mozilla {
namespace a11y {
typedef class HyperTextAccessible HyperTextAccessibleWrap;
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,21 @@
/* -*- 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_ImageAccessibleWrap_h__
#define mozilla_a11y_ImageAccessibleWrap_h__
#include "ImageAccessible.h"
namespace mozilla {
namespace a11y {
typedef class ImageAccessible ImageAccessibleWrap;
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,44 @@
/* -*- 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 ATK_INTERFACE_INIT_FUNCS_H_
#define ATK_INTERFACE_INIT_FUNCS_H_
#include <atk/atk.h>
namespace mozilla {
namespace a11y {
class AccessibleWrap;
} // namespace a11y
} // namespace mozilla
extern "C" {
void actionInterfaceInitCB(AtkActionIface* aIface);
void componentInterfaceInitCB(AtkComponentIface* aIface);
void documentInterfaceInitCB(AtkDocumentIface *aIface);
void editableTextInterfaceInitCB(AtkEditableTextIface* aIface);
void hyperlinkImplInterfaceInitCB(AtkHyperlinkImplIface *aIface);
void hypertextInterfaceInitCB(AtkHypertextIface* aIface);
void imageInterfaceInitCB(AtkImageIface* aIface);
void selectionInterfaceInitCB(AtkSelectionIface* aIface);
void tableInterfaceInitCB(AtkTableIface *aIface);
void tableCellInterfaceInitCB(AtkTableCellIface *aIface);
void textInterfaceInitCB(AtkTextIface* aIface);
void valueInterfaceInitCB(AtkValueIface *aIface);
}
/**
* XXX these should live in a file of utils for atk.
*/
AtkObject* refAccessibleAtPointHelper(AtkObject* aAtkObj,
gint aX, gint aY, AtkCoordType aCoordType);
void getExtentsHelper(AtkObject* aAtkObj,
gint* aX, gint* aY, gint* aWidth, gint* aHeight,
AtkCoordType aCoordType);
#endif // ATK_INTERFACE_INIT_FUNCS_H_

377
accessible/atk/Platform.cpp Normal file
View file

@ -0,0 +1,377 @@
/* -*- 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 "Platform.h"
#include "nsIAccessibleEvent.h"
#include "nsIGConfService.h"
#include "nsIServiceManager.h"
#include "nsMai.h"
#include "AtkSocketAccessible.h"
#include "prenv.h"
#include "prlink.h"
#ifdef MOZ_ENABLE_DBUS
#include <dbus/dbus.h>
#endif
#include <gtk/gtk.h>
#if (MOZ_WIDGET_GTK == 3)
extern "C" __attribute__((weak,visibility("default"))) int atk_bridge_adaptor_init(int*, char **[]);
#endif
using namespace mozilla;
using namespace mozilla::a11y;
int atkMajorVersion = 1, atkMinorVersion = 12;
GType (*gAtkTableCellGetTypeFunc)();
extern "C" {
typedef GType (* AtkGetTypeType) (void);
typedef void (*GnomeAccessibilityInit) (void);
typedef void (*GnomeAccessibilityShutdown) (void);
}
static PRLibrary* sATKLib = nullptr;
static const char sATKLibName[] = "libatk-1.0.so.0";
static const char sATKHyperlinkImplGetTypeSymbol[] =
"atk_hyperlink_impl_get_type";
gboolean toplevel_event_watcher(GSignalInvocationHint*, guint, const GValue*,
gpointer);
static bool sToplevel_event_hook_added = false;
static gulong sToplevel_show_hook = 0;
static gulong sToplevel_hide_hook = 0;
GType g_atk_hyperlink_impl_type = G_TYPE_INVALID;
struct GnomeAccessibilityModule
{
const char *libName;
PRLibrary *lib;
const char *initName;
GnomeAccessibilityInit init;
const char *shutdownName;
GnomeAccessibilityShutdown shutdown;
};
static GnomeAccessibilityModule sAtkBridge = {
#ifdef AIX
"libatk-bridge.a(libatk-bridge.so.0)", nullptr,
#else
"libatk-bridge.so", nullptr,
#endif
"gnome_accessibility_module_init", nullptr,
"gnome_accessibility_module_shutdown", nullptr
};
#if (MOZ_WIDGET_GTK == 2)
static GnomeAccessibilityModule sGail = {
"libgail.so", nullptr,
"gnome_accessibility_module_init", nullptr,
"gnome_accessibility_module_shutdown", nullptr
};
#endif
static nsresult
LoadGtkModule(GnomeAccessibilityModule& aModule)
{
NS_ENSURE_ARG(aModule.libName);
if (!(aModule.lib = PR_LoadLibrary(aModule.libName))) {
//try to load the module with "gtk-2.0/modules" appended
char *curLibPath = PR_GetLibraryPath();
nsAutoCString libPath(curLibPath);
#if defined(LINUX) && defined(__x86_64__)
libPath.AppendLiteral(":/usr/lib64:/usr/lib");
#else
libPath.AppendLiteral(":/usr/lib");
#endif
PR_FreeLibraryName(curLibPath);
int16_t loc1 = 0, loc2 = 0;
int16_t subLen = 0;
while (loc2 >= 0) {
loc2 = libPath.FindChar(':', loc1);
if (loc2 < 0)
subLen = libPath.Length() - loc1;
else
subLen = loc2 - loc1;
nsAutoCString sub(Substring(libPath, loc1, subLen));
#if (MOZ_WIDGET_GTK == 2)
sub.AppendLiteral("/gtk-2.0/modules/");
#else
sub.AppendLiteral("/gtk-3.0/modules/");
#endif
sub.Append(aModule.libName);
aModule.lib = PR_LoadLibrary(sub.get());
if (aModule.lib)
break;
loc1 = loc2+1;
}
if (!aModule.lib)
return NS_ERROR_FAILURE;
}
//we have loaded the library, try to get the function ptrs
if (!(aModule.init = PR_FindFunctionSymbol(aModule.lib,
aModule.initName)) ||
!(aModule.shutdown = PR_FindFunctionSymbol(aModule.lib,
aModule.shutdownName))) {
//fail, :(
PR_UnloadLibrary(aModule.lib);
aModule.lib = nullptr;
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void
a11y::PlatformInit()
{
if (!ShouldA11yBeEnabled())
return;
sATKLib = PR_LoadLibrary(sATKLibName);
if (!sATKLib)
return;
AtkGetTypeType pfn_atk_hyperlink_impl_get_type =
(AtkGetTypeType) PR_FindFunctionSymbol(sATKLib, sATKHyperlinkImplGetTypeSymbol);
if (pfn_atk_hyperlink_impl_get_type)
g_atk_hyperlink_impl_type = pfn_atk_hyperlink_impl_get_type();
AtkGetTypeType pfn_atk_socket_get_type = (AtkGetTypeType)
PR_FindFunctionSymbol(sATKLib, AtkSocketAccessible::sATKSocketGetTypeSymbol);
if (pfn_atk_socket_get_type) {
AtkSocketAccessible::g_atk_socket_type = pfn_atk_socket_get_type();
AtkSocketAccessible::g_atk_socket_embed = (AtkSocketEmbedType)
PR_FindFunctionSymbol(sATKLib, AtkSocketAccessible ::sATKSocketEmbedSymbol);
AtkSocketAccessible::gCanEmbed =
AtkSocketAccessible::g_atk_socket_type != G_TYPE_INVALID &&
AtkSocketAccessible::g_atk_socket_embed;
}
gAtkTableCellGetTypeFunc = (GType (*)())
PR_FindFunctionSymbol(sATKLib, "atk_table_cell_get_type");
const char* (*atkGetVersion)() =
(const char* (*)()) PR_FindFunctionSymbol(sATKLib, "atk_get_version");
if (atkGetVersion) {
const char* version = atkGetVersion();
if (version) {
char* endPtr = nullptr;
atkMajorVersion = strtol(version, &endPtr, 10);
if (*endPtr == '.')
atkMinorVersion = strtol(endPtr + 1, &endPtr, 10);
}
}
#if (MOZ_WIDGET_GTK == 2)
// Load and initialize gail library.
nsresult rv = LoadGtkModule(sGail);
if (NS_SUCCEEDED(rv))
(*sGail.init)();
#endif
// Initialize the MAI Utility class, it will overwrite gail_util.
g_type_class_unref(g_type_class_ref(mai_util_get_type()));
// Init atk-bridge now
PR_SetEnv("NO_AT_BRIDGE=0");
#if (MOZ_WIDGET_GTK == 3)
if (atk_bridge_adaptor_init) {
atk_bridge_adaptor_init(nullptr, nullptr);
} else
#endif
{
nsresult rv = LoadGtkModule(sAtkBridge);
if (NS_SUCCEEDED(rv)) {
(*sAtkBridge.init)();
}
}
if (!sToplevel_event_hook_added) {
sToplevel_event_hook_added = true;
sToplevel_show_hook =
g_signal_add_emission_hook(g_signal_lookup("show", GTK_TYPE_WINDOW),
0, toplevel_event_watcher,
reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_SHOW),
nullptr);
sToplevel_hide_hook =
g_signal_add_emission_hook(g_signal_lookup("hide", GTK_TYPE_WINDOW), 0,
toplevel_event_watcher,
reinterpret_cast<gpointer>(nsIAccessibleEvent::EVENT_HIDE),
nullptr);
}
}
void
a11y::PlatformShutdown()
{
if (sToplevel_event_hook_added) {
sToplevel_event_hook_added = false;
g_signal_remove_emission_hook(g_signal_lookup("show", GTK_TYPE_WINDOW),
sToplevel_show_hook);
g_signal_remove_emission_hook(g_signal_lookup("hide", GTK_TYPE_WINDOW),
sToplevel_hide_hook);
}
if (sAtkBridge.lib) {
// Do not shutdown/unload atk-bridge,
// an exit function registered will take care of it
// if (sAtkBridge.shutdown)
// (*sAtkBridge.shutdown)();
// PR_UnloadLibrary(sAtkBridge.lib);
sAtkBridge.lib = nullptr;
sAtkBridge.init = nullptr;
sAtkBridge.shutdown = nullptr;
}
#if (MOZ_WIDGET_GTK == 2)
if (sGail.lib) {
// Do not shutdown gail because
// 1) Maybe it's not init-ed by us. e.g. GtkEmbed
// 2) We need it to avoid assert in spi_atk_tidy_windows
// if (sGail.shutdown)
// (*sGail.shutdown)();
// PR_UnloadLibrary(sGail.lib);
sGail.lib = nullptr;
sGail.init = nullptr;
sGail.shutdown = nullptr;
}
#endif
// if (sATKLib) {
// PR_UnloadLibrary(sATKLib);
// sATKLib = nullptr;
// }
}
static const char sAccEnv [] = "GNOME_ACCESSIBILITY";
#ifdef MOZ_ENABLE_DBUS
static DBusPendingCall *sPendingCall = nullptr;
#endif
void
a11y::PreInit()
{
#ifdef MOZ_ENABLE_DBUS
static bool sChecked = FALSE;
if (sChecked)
return;
sChecked = TRUE;
// dbus is only checked if GNOME_ACCESSIBILITY is unset
// also make sure that a session bus address is available to prevent dbus from
// starting a new one. Dbus confuses the test harness when it creates a new
// process (see bug 693343)
if (PR_GetEnv(sAccEnv) || !PR_GetEnv("DBUS_SESSION_BUS_ADDRESS"))
return;
DBusConnection* bus = dbus_bus_get(DBUS_BUS_SESSION, nullptr);
if (!bus)
return;
dbus_connection_set_exit_on_disconnect(bus, FALSE);
static const char* iface = "org.a11y.Status";
static const char* member = "IsEnabled";
DBusMessage *message;
message = dbus_message_new_method_call("org.a11y.Bus", "/org/a11y/bus",
"org.freedesktop.DBus.Properties",
"Get");
if (!message)
goto dbus_done;
dbus_message_append_args(message, DBUS_TYPE_STRING, &iface,
DBUS_TYPE_STRING, &member, DBUS_TYPE_INVALID);
dbus_connection_send_with_reply(bus, message, &sPendingCall, 1000);
dbus_message_unref(message);
dbus_done:
dbus_connection_unref(bus);
#endif
}
bool
a11y::ShouldA11yBeEnabled()
{
static bool sChecked = false, sShouldEnable = false;
if (sChecked)
return sShouldEnable;
sChecked = true;
EPlatformDisabledState disabledState = PlatformDisabledState();
if (disabledState == ePlatformIsDisabled)
return sShouldEnable = false;
// check if accessibility enabled/disabled by environment variable
const char* envValue = PR_GetEnv(sAccEnv);
if (envValue)
return sShouldEnable = !!atoi(envValue);
#ifdef MOZ_ENABLE_DBUS
PreInit();
bool dbusSuccess = false;
DBusMessage *reply = nullptr;
if (!sPendingCall)
goto dbus_done;
dbus_pending_call_block(sPendingCall);
reply = dbus_pending_call_steal_reply(sPendingCall);
dbus_pending_call_unref(sPendingCall);
sPendingCall = nullptr;
if (!reply ||
dbus_message_get_type(reply) != DBUS_MESSAGE_TYPE_METHOD_RETURN ||
strcmp(dbus_message_get_signature (reply), DBUS_TYPE_VARIANT_AS_STRING))
goto dbus_done;
DBusMessageIter iter, iter_variant, iter_struct;
dbus_bool_t dResult;
dbus_message_iter_init(reply, &iter);
dbus_message_iter_recurse (&iter, &iter_variant);
switch (dbus_message_iter_get_arg_type(&iter_variant)) {
case DBUS_TYPE_STRUCT:
// at-spi2-core 2.2.0-2.2.1 had a bug where it returned a struct
dbus_message_iter_recurse(&iter_variant, &iter_struct);
if (dbus_message_iter_get_arg_type(&iter_struct) == DBUS_TYPE_BOOLEAN) {
dbus_message_iter_get_basic(&iter_struct, &dResult);
sShouldEnable = dResult;
dbusSuccess = true;
}
break;
case DBUS_TYPE_BOOLEAN:
dbus_message_iter_get_basic(&iter_variant, &dResult);
sShouldEnable = dResult;
dbusSuccess = true;
break;
default:
break;
}
dbus_done:
if (reply)
dbus_message_unref(reply);
if (dbusSuccess)
return sShouldEnable;
#endif
//check gconf-2 setting
#define GCONF_A11Y_KEY "/desktop/gnome/interface/accessibility"
nsresult rv = NS_OK;
nsCOMPtr<nsIGConfService> gconf =
do_GetService(NS_GCONFSERVICE_CONTRACTID, &rv);
if (NS_SUCCEEDED(rv) && gconf)
gconf->GetBool(NS_LITERAL_CSTRING(GCONF_A11Y_KEY), &sShouldEnable);
return sShouldEnable;
}

View file

@ -0,0 +1,24 @@
/* -*- 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 "RootAccessibleWrap.h"
#include "nsMai.h"
using namespace mozilla::a11y;
GtkWindowAccessible::GtkWindowAccessible(AtkObject* aAccessible) :
DummyAccessible()
{
g_object_ref(aAccessible);
mAtkObject = aAccessible;
}
GtkWindowAccessible::~GtkWindowAccessible()
{
g_object_unref(mAtkObject);
mAtkObject = nullptr;
}

View file

@ -0,0 +1,34 @@
/* -*- 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_RootAccessibleWrap_h__
#define mozilla_a11y_RootAccessibleWrap_h__
#include "BaseAccessibles.h"
#include "RootAccessible.h"
namespace mozilla {
namespace a11y {
typedef RootAccessible RootAccessibleWrap;
/* GtkWindowAccessible is the accessible class for gtk+ native window.
* The instance of GtkWindowAccessible is a child of MaiAppRoot instance.
* It is added into root when the toplevel window is created, and removed
* from root when the toplevel window is destroyed.
*/
class GtkWindowAccessible final : public DummyAccessible
{
public:
explicit GtkWindowAccessible(AtkObject* aAccessible);
virtual ~GtkWindowAccessible();
};
} // namespace a11y
} // namespace mozilla
#endif /* mozilla_a11y_Root_Accessible_Wrap_h__ */

View file

@ -0,0 +1,20 @@
/* -*- 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_TextLeafAccessibleWrap_h__
#define mozilla_a11y_TextLeafAccessibleWrap_h__
#include "TextLeafAccessible.h"
namespace mozilla {
namespace a11y {
typedef class TextLeafAccessible TextLeafAccessibleWrap;
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,411 @@
/* -*- 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 "ApplicationAccessibleWrap.h"
#include "mozilla/Likely.h"
#include "nsAccessibilityService.h"
#include "nsMai.h"
#include <atk/atk.h>
#include <gtk/gtk.h>
#include <string.h>
using namespace mozilla;
using namespace mozilla::a11y;
typedef AtkUtil MaiUtil;
typedef AtkUtilClass MaiUtilClass;
#define MAI_VERSION MOZILLA_VERSION
#define MAI_NAME "Gecko"
extern "C" {
static guint (*gail_add_global_event_listener)(GSignalEmissionHook listener,
const gchar* event_type);
static void (*gail_remove_global_event_listener) (guint remove_listener);
static void (*gail_remove_key_event_listener) (guint remove_listener);
static AtkObject* (*gail_get_root)();
}
struct MaiUtilListenerInfo
{
gint key;
guint signal_id;
gulong hook_id;
// For window create/destory/minimize/maximize/restore/activate/deactivate
// events, we'll chain gail_util's add/remove_global_event_listener.
// So we store the listenerid returned by gail's add_global_event_listener
// in this structure to call gail's remove_global_event_listener later.
guint gail_listenerid;
};
static GHashTable* sListener_list = nullptr;
static gint sListener_idx = 1;
extern "C" {
static guint
add_listener (GSignalEmissionHook listener,
const gchar *object_type,
const gchar *signal,
const gchar *hook_data,
guint gail_listenerid = 0)
{
GType type;
guint signal_id;
gint rc = 0;
type = g_type_from_name(object_type);
if (type) {
signal_id = g_signal_lookup(signal, type);
if (signal_id > 0) {
MaiUtilListenerInfo *listener_info;
rc = sListener_idx;
listener_info = (MaiUtilListenerInfo *)
g_malloc(sizeof(MaiUtilListenerInfo));
listener_info->key = sListener_idx;
listener_info->hook_id =
g_signal_add_emission_hook(signal_id, 0, listener,
g_strdup(hook_data),
(GDestroyNotify)g_free);
listener_info->signal_id = signal_id;
listener_info->gail_listenerid = gail_listenerid;
g_hash_table_insert(sListener_list, &(listener_info->key),
listener_info);
sListener_idx++;
}
else {
g_warning("Invalid signal type %s\n", signal);
}
}
else {
g_warning("Invalid object type %s\n", object_type);
}
return rc;
}
static guint
mai_util_add_global_event_listener(GSignalEmissionHook listener,
const gchar *event_type)
{
guint rc = 0;
gchar **split_string;
split_string = g_strsplit (event_type, ":", 3);
if (split_string) {
if (!strcmp ("window", split_string[0])) {
guint gail_listenerid = 0;
if (gail_add_global_event_listener) {
// call gail's function to track gtk native window events
gail_listenerid =
gail_add_global_event_listener(listener, event_type);
}
rc = add_listener (listener, "MaiAtkObject", split_string[1],
event_type, gail_listenerid);
}
else {
rc = add_listener (listener, split_string[1], split_string[2],
event_type);
}
g_strfreev(split_string);
}
return rc;
}
static void
mai_util_remove_global_event_listener(guint remove_listener)
{
if (remove_listener > 0) {
MaiUtilListenerInfo *listener_info;
gint tmp_idx = remove_listener;
listener_info = (MaiUtilListenerInfo *)
g_hash_table_lookup(sListener_list, &tmp_idx);
if (listener_info != nullptr) {
if (gail_remove_global_event_listener &&
listener_info->gail_listenerid) {
gail_remove_global_event_listener(listener_info->gail_listenerid);
}
/* Hook id of 0 and signal id of 0 are invalid */
if (listener_info->hook_id != 0 && listener_info->signal_id != 0) {
/* Remove the emission hook */
g_signal_remove_emission_hook(listener_info->signal_id,
listener_info->hook_id);
/* Remove the element from the hash */
g_hash_table_remove(sListener_list, &tmp_idx);
}
else {
g_warning("Invalid listener hook_id %ld or signal_id %d\n",
listener_info->hook_id, listener_info->signal_id);
}
}
else {
// atk-bridge is initialized with gail (e.g. yelp)
// try gail_remove_global_event_listener
if (gail_remove_global_event_listener) {
return gail_remove_global_event_listener(remove_listener);
}
g_warning("No listener with the specified listener id %d",
remove_listener);
}
}
else {
g_warning("Invalid listener_id %d", remove_listener);
}
}
static AtkKeyEventStruct *
atk_key_event_from_gdk_event_key (GdkEventKey *key)
{
AtkKeyEventStruct *event = g_new0(AtkKeyEventStruct, 1);
switch (key->type) {
case GDK_KEY_PRESS:
event->type = ATK_KEY_EVENT_PRESS;
break;
case GDK_KEY_RELEASE:
event->type = ATK_KEY_EVENT_RELEASE;
break;
default:
g_assert_not_reached ();
return nullptr;
}
event->state = key->state;
event->keyval = key->keyval;
event->length = key->length;
if (key->string && key->string [0] &&
(key->state & GDK_CONTROL_MASK ||
g_unichar_isgraph (g_utf8_get_char (key->string)))) {
event->string = key->string;
}
else if (key->type == GDK_KEY_PRESS ||
key->type == GDK_KEY_RELEASE) {
event->string = gdk_keyval_name (key->keyval);
}
event->keycode = key->hardware_keycode;
event->timestamp = key->time;
return event;
}
struct MaiKeyEventInfo
{
AtkKeyEventStruct *key_event;
gpointer func_data;
};
union AtkKeySnoopFuncPointer
{
AtkKeySnoopFunc func_ptr;
gpointer data;
};
static gboolean
notify_hf(gpointer key, gpointer value, gpointer data)
{
MaiKeyEventInfo *info = (MaiKeyEventInfo *)data;
AtkKeySnoopFuncPointer atkKeySnoop;
atkKeySnoop.data = value;
return (atkKeySnoop.func_ptr)(info->key_event, info->func_data) ? TRUE : FALSE;
}
static void
insert_hf(gpointer key, gpointer value, gpointer data)
{
GHashTable *new_table = (GHashTable *) data;
g_hash_table_insert (new_table, key, value);
}
static GHashTable* sKey_listener_list = nullptr;
static gint
mai_key_snooper(GtkWidget *the_widget, GdkEventKey *event, gpointer func_data)
{
/* notify each AtkKeySnoopFunc in turn... */
MaiKeyEventInfo *info = g_new0(MaiKeyEventInfo, 1);
gint consumed = 0;
if (sKey_listener_list) {
GHashTable *new_hash = g_hash_table_new(nullptr, nullptr);
g_hash_table_foreach (sKey_listener_list, insert_hf, new_hash);
info->key_event = atk_key_event_from_gdk_event_key (event);
info->func_data = func_data;
consumed = g_hash_table_foreach_steal (new_hash, notify_hf, info);
g_hash_table_destroy (new_hash);
g_free(info->key_event);
}
g_free(info);
return (consumed ? 1 : 0);
}
static guint sKey_snooper_id = 0;
static guint
mai_util_add_key_event_listener(AtkKeySnoopFunc listener, gpointer data)
{
if (MOZ_UNLIKELY(!listener)) {
return 0;
}
static guint key = 0;
if (!sKey_listener_list) {
sKey_listener_list = g_hash_table_new(nullptr, nullptr);
}
// If we have no registered event listeners then we need to (re)install the
// key event snooper.
if (g_hash_table_size(sKey_listener_list) == 0) {
sKey_snooper_id = gtk_key_snooper_install(mai_key_snooper, data);
}
AtkKeySnoopFuncPointer atkKeySnoop;
atkKeySnoop.func_ptr = listener;
key++;
g_hash_table_insert(sKey_listener_list, GUINT_TO_POINTER(key),
atkKeySnoop.data);
return key;
}
static void
mai_util_remove_key_event_listener (guint remove_listener)
{
if (!sKey_listener_list) {
// atk-bridge is initialized with gail (e.g. yelp)
// try gail_remove_key_event_listener
return gail_remove_key_event_listener(remove_listener);
}
g_hash_table_remove(sKey_listener_list, GUINT_TO_POINTER (remove_listener));
if (g_hash_table_size(sKey_listener_list) == 0) {
gtk_key_snooper_remove(sKey_snooper_id);
}
}
static AtkObject*
mai_util_get_root()
{
ApplicationAccessible* app = ApplicationAcc();
if (app)
return app->GetAtkObject();
// We've shutdown, try to use gail instead
// (to avoid assert in spi_atk_tidy_windows())
// XXX tbsaunde then why didn't we replace the gail atk_util impl?
if (gail_get_root)
return gail_get_root();
return nullptr;
}
static const gchar*
mai_util_get_toolkit_name()
{
return MAI_NAME;
}
static const gchar*
mai_util_get_toolkit_version()
{
return MAI_VERSION;
}
static void
_listener_info_destroy(gpointer data)
{
g_free(data);
}
static void
window_added (AtkObject *atk_obj,
guint index,
AtkObject *child)
{
if (!IS_MAI_OBJECT(child))
return;
static guint id = g_signal_lookup ("create", MAI_TYPE_ATK_OBJECT);
g_signal_emit (child, id, 0);
}
static void
window_removed (AtkObject *atk_obj,
guint index,
AtkObject *child)
{
if (!IS_MAI_OBJECT(child))
return;
static guint id = g_signal_lookup ("destroy", MAI_TYPE_ATK_OBJECT);
g_signal_emit (child, id, 0);
}
static void
UtilInterfaceInit(MaiUtilClass* klass)
{
AtkUtilClass *atk_class;
gpointer data;
data = g_type_class_peek(ATK_TYPE_UTIL);
atk_class = ATK_UTIL_CLASS(data);
// save gail function pointer
gail_add_global_event_listener = atk_class->add_global_event_listener;
gail_remove_global_event_listener = atk_class->remove_global_event_listener;
gail_remove_key_event_listener = atk_class->remove_key_event_listener;
gail_get_root = atk_class->get_root;
atk_class->add_global_event_listener =
mai_util_add_global_event_listener;
atk_class->remove_global_event_listener =
mai_util_remove_global_event_listener;
atk_class->add_key_event_listener = mai_util_add_key_event_listener;
atk_class->remove_key_event_listener = mai_util_remove_key_event_listener;
atk_class->get_root = mai_util_get_root;
atk_class->get_toolkit_name = mai_util_get_toolkit_name;
atk_class->get_toolkit_version = mai_util_get_toolkit_version;
sListener_list = g_hash_table_new_full(g_int_hash, g_int_equal, nullptr,
_listener_info_destroy);
// Keep track of added/removed windows.
AtkObject *root = atk_get_root ();
g_signal_connect (root, "children-changed::add", (GCallback) window_added, nullptr);
g_signal_connect (root, "children-changed::remove", (GCallback) window_removed, nullptr);
}
}
GType
mai_util_get_type()
{
static GType type = 0;
if (!type) {
static const GTypeInfo tinfo = {
sizeof(MaiUtilClass),
(GBaseInitFunc) nullptr, /* base init */
(GBaseFinalizeFunc) nullptr, /* base finalize */
(GClassInitFunc) UtilInterfaceInit, /* class init */
(GClassFinalizeFunc) nullptr, /* class finalize */
nullptr, /* class data */
sizeof(MaiUtil), /* instance size */
0, /* nb preallocs */
(GInstanceInitFunc) nullptr, /* instance init */
nullptr /* value table */
};
type = g_type_register_static(ATK_TYPE_UTIL,
"MaiUtil", &tinfo, GTypeFlags(0));
}
return type;
}

View file

@ -0,0 +1,21 @@
/* -*- 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_XULListboxAccessibleWrap_h__
#define mozilla_a11y_XULListboxAccessibleWrap_h__
#include "XULListboxAccessible.h"
namespace mozilla {
namespace a11y {
typedef class XULListboxAccessible XULListboxAccessibleWrap;
typedef class XULListCellAccessible XULListCellAccessibleWrap;
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,20 @@
/* -*- 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_XULMenuAccessibleWrap_h__
#define mozilla_a11y_XULMenuAccessibleWrap_h__
#include "XULMenuAccessible.h"
namespace mozilla {
namespace a11y {
typedef class XULMenuitemAccessible XULMenuitemAccessibleWrap;
} // namespace a11y
} // namespace mozilla
#endif

View file

@ -0,0 +1,22 @@
/* -*- 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_XULTreeGridAccessibleWrap_h__
#define mozilla_a11y_XULTreeGridAccessibleWrap_h__
#include "XULTreeGridAccessible.h"
namespace mozilla {
namespace a11y {
typedef class XULTreeGridAccessible XULTreeGridAccessibleWrap;
typedef class XULTreeGridCellAccessible XULTreeGridCellAccessibleWrap;
} // namespace a11y
} // namespace mozilla
#endif

63
accessible/atk/moz.build Normal file
View file

@ -0,0 +1,63 @@
# -*- 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.mozilla.a11y += [
'AccessibleWrap.h',
'HyperTextAccessibleWrap.h',
]
SOURCES += [
'AccessibleWrap.cpp',
'ApplicationAccessibleWrap.cpp',
'AtkSocketAccessible.cpp',
'DocAccessibleWrap.cpp',
'nsMaiHyperlink.cpp',
'nsMaiInterfaceAction.cpp',
'nsMaiInterfaceComponent.cpp',
'nsMaiInterfaceDocument.cpp',
'nsMaiInterfaceEditableText.cpp',
'nsMaiInterfaceHyperlinkImpl.cpp',
'nsMaiInterfaceHypertext.cpp',
'nsMaiInterfaceImage.cpp',
'nsMaiInterfaceSelection.cpp',
'nsMaiInterfaceTable.cpp',
'nsMaiInterfaceTableCell.cpp',
'nsMaiInterfaceText.cpp',
'nsMaiInterfaceValue.cpp',
'Platform.cpp',
'RootAccessibleWrap.cpp',
'UtilInterface.cpp',
]
LOCAL_INCLUDES += [
'/accessible/base',
'/accessible/generic',
'/accessible/html',
'/accessible/ipc',
'/accessible/ipc/other',
'/accessible/xpcom',
'/accessible/xul',
'/other-licenses/atk-1.0',
]
FINAL_LIBRARY = 'xul'
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
CFLAGS += CONFIG['TK_CFLAGS']
CXXFLAGS += CONFIG['TK_CFLAGS']
if CONFIG['MOZ_ENABLE_DBUS']:
CXXFLAGS += CONFIG['MOZ_DBUS_CFLAGS']
include('/ipc/chromium/chromium-config.mozbuild')
if CONFIG['CLANG_CXX'] or CONFIG['GNU_CXX']:
# Used in G_DEFINE_TYPE_EXTENDED macro, probably fixed in newer glib /
# gobject headers. See bug 1243331 comment 3.
CXXFLAGS += [
'-Wno-error=shadow',
'-Wno-unused-local-typedefs',
]

149
accessible/atk/nsMai.h Normal file
View file

@ -0,0 +1,149 @@
/* -*- 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 __NS_MAI_H__
#define __NS_MAI_H__
#include <atk/atk.h>
#include <glib.h>
#include <glib-object.h>
#include "AccessibleOrProxy.h"
#include "AccessibleWrap.h"
namespace mozilla {
namespace a11y {
class ProxyAccessible;
}
}
#define MAI_TYPE_ATK_OBJECT (mai_atk_object_get_type ())
#define MAI_ATK_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
MAI_TYPE_ATK_OBJECT, MaiAtkObject))
#define MAI_ATK_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \
MAI_TYPE_ATK_OBJECT, \
MaiAtkObjectClass))
#define IS_MAI_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
MAI_TYPE_ATK_OBJECT))
#define IS_MAI_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \
MAI_TYPE_ATK_OBJECT))
#define MAI_ATK_OBJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \
MAI_TYPE_ATK_OBJECT, \
MaiAtkObjectClass))
GType mai_atk_object_get_type(void);
GType mai_util_get_type();
extern "C" GType mai_atk_socket_get_type(void);
/* MaiAtkSocket */
#define MAI_TYPE_ATK_SOCKET (mai_atk_socket_get_type ())
#define MAI_ATK_SOCKET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
MAI_TYPE_ATK_SOCKET, MaiAtkSocket))
#define MAI_IS_ATK_SOCKET(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
MAI_TYPE_ATK_SOCKET))
#define MAI_ATK_SOCKET_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
MAI_TYPE_ATK_SOCKET,\
MaiAtkSocketClass))
#define MAI_IS_ATK_SOCKET_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
MAI_TYPE_ATK_SOCKET))
#define MAI_ATK_SOCKET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
MAI_TYPE_ATK_SOCKET,\
MaiAtkSocketClass))
typedef struct _MaiAtkSocket
{
AtkSocket parent;
mozilla::a11y::AccessibleWrap* accWrap;
} MaiAtkSocket;
typedef struct _MaiAtkSocketClass
{
AtkSocketClass parent_class;
} MaiAtkSocketClass;
// This is a pointer to the atk_table_cell_get_type function if we are using
// a version of atk that defines that.
extern "C" GType (*gAtkTableCellGetTypeFunc)();
mozilla::a11y::AccessibleWrap* GetAccessibleWrap(AtkObject* aAtkObj);
mozilla::a11y::ProxyAccessible* GetProxy(AtkObject* aAtkObj);
mozilla::a11y::AccessibleOrProxy GetInternalObj(AtkObject* aObj);
AtkObject* GetWrapperFor(mozilla::a11y::ProxyAccessible* aProxy);
AtkObject* GetWrapperFor(mozilla::a11y::AccessibleOrProxy aObj);
extern int atkMajorVersion, atkMinorVersion;
/**
* Return true if the loaded version of libatk-1.0.so is at least
* aMajor.aMinor.0.
*/
static inline bool
IsAtkVersionAtLeast(int aMajor, int aMinor)
{
return aMajor < atkMajorVersion ||
(aMajor == atkMajorVersion && aMinor <= atkMinorVersion);
}
// This is or'd with the pointer in MaiAtkObject::accWrap if the wrap-ee is a
// proxy.
static const uintptr_t IS_PROXY = 1;
/**
* This MaiAtkObject is a thin wrapper, in the MAI namespace, for AtkObject
*/
struct MaiAtkObject
{
AtkObject parent;
/*
* The AccessibleWrap whose properties and features are exported
* via this object instance.
*/
mozilla::a11y::AccessibleOrProxy accWrap;
/*
* Get the AtkHyperlink for this atk object.
*/
AtkHyperlink* GetAtkHyperlink();
/*
* Shutdown this AtkObject.
*/
void Shutdown();
/*
* Notify atk of a state change on this AtkObject.
*/
void FireStateChangeEvent(uint64_t aState, bool aEnabled);
/*
* Notify ATK of a text change within this ATK object.
*/
void FireTextChangeEvent(const nsString& aStr, int32_t aStart, uint32_t aLen,
bool aIsInsert, bool aIsFromUser);
/**
* Notify ATK of a shown or hidden subtree rooted at aObject whose parent is
* aParent
*/
void FireAtkShowHideEvent(AtkObject* aParent, bool aIsAdded, bool aFromUser);
private:
/*
* do we have text-remove and text-insert signals if not we need to use
* text-changed see AccessibleWrap::FireAtkTextChangedEvent() and
* bug 619002
*/
enum EAvailableAtkSignals {
eUnknown,
eHaveNewAtkTextSignals,
eNoNewAtkSignals
};
static EAvailableAtkSignals gAvailableAtkSignals;
};
#endif /* __NS_MAI_H__ */

View file

@ -0,0 +1,262 @@
/* -*- 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 "nsIURI.h"
#include "nsMaiHyperlink.h"
#include "mozilla/a11y/ProxyAccessible.h"
using namespace mozilla::a11y;
/* MaiAtkHyperlink */
#define MAI_TYPE_ATK_HYPERLINK (mai_atk_hyperlink_get_type ())
#define MAI_ATK_HYPERLINK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj),\
MAI_TYPE_ATK_HYPERLINK, MaiAtkHyperlink))
#define MAI_ATK_HYPERLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass),\
MAI_TYPE_ATK_HYPERLINK, MaiAtkHyperlinkClass))
#define MAI_IS_ATK_HYPERLINK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj),\
MAI_TYPE_ATK_HYPERLINK))
#define MAI_IS_ATK_HYPERLINK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),\
MAI_TYPE_ATK_HYPERLINK))
#define MAI_ATK_HYPERLINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj),\
MAI_TYPE_ATK_HYPERLINK, MaiAtkHyperlinkClass))
/**
* This MaiAtkHyperlink is a thin wrapper, in the MAI namespace,
* for AtkHyperlink
*/
struct MaiAtkHyperlink
{
AtkHyperlink parent;
/*
* The MaiHyperlink whose properties and features are exported via this
* hyperlink instance.
*/
MaiHyperlink *maiHyperlink;
};
struct MaiAtkHyperlinkClass
{
AtkHyperlinkClass parent_class;
};
GType mai_atk_hyperlink_get_type(void);
G_BEGIN_DECLS
/* callbacks for AtkHyperlink */
static void classInitCB(AtkHyperlinkClass *aClass);
static void finalizeCB(GObject *aObj);
/* callbacks for AtkHyperlink virtual functions */
static gchar *getUriCB(AtkHyperlink *aLink, gint aLinkIndex);
static AtkObject *getObjectCB(AtkHyperlink *aLink, gint aLinkIndex);
static gint getEndIndexCB(AtkHyperlink *aLink);
static gint getStartIndexCB(AtkHyperlink *aLink);
static gboolean isValidCB(AtkHyperlink *aLink);
static gint getAnchorCountCB(AtkHyperlink *aLink);
G_END_DECLS
static gpointer parent_class = nullptr;
static MaiHyperlink*
GetMaiHyperlink(AtkHyperlink *aHyperlink)
{
NS_ENSURE_TRUE(MAI_IS_ATK_HYPERLINK(aHyperlink), nullptr);
MaiHyperlink * maiHyperlink =
MAI_ATK_HYPERLINK(aHyperlink)->maiHyperlink;
NS_ENSURE_TRUE(maiHyperlink != nullptr, nullptr);
NS_ENSURE_TRUE(maiHyperlink->GetAtkHyperlink() == aHyperlink, nullptr);
return maiHyperlink;
}
GType
mai_atk_hyperlink_get_type(void)
{
static GType type = 0;
if (!type) {
static const GTypeInfo tinfo = {
sizeof(MaiAtkHyperlinkClass),
(GBaseInitFunc)nullptr,
(GBaseFinalizeFunc)nullptr,
(GClassInitFunc)classInitCB,
(GClassFinalizeFunc)nullptr,
nullptr, /* class data */
sizeof(MaiAtkHyperlink), /* instance size */
0, /* nb preallocs */
(GInstanceInitFunc)nullptr,
nullptr /* value table */
};
type = g_type_register_static(ATK_TYPE_HYPERLINK,
"MaiAtkHyperlink",
&tinfo, GTypeFlags(0));
}
return type;
}
MaiHyperlink::MaiHyperlink(AccessibleOrProxy aHyperLink) :
mHyperlink(aHyperLink),
mMaiAtkHyperlink(nullptr)
{
mMaiAtkHyperlink =
reinterpret_cast<AtkHyperlink *>
(g_object_new(mai_atk_hyperlink_get_type(), nullptr));
NS_ASSERTION(mMaiAtkHyperlink, "OUT OF MEMORY");
if (!mMaiAtkHyperlink)
return;
MAI_ATK_HYPERLINK(mMaiAtkHyperlink)->maiHyperlink = this;
}
MaiHyperlink::~MaiHyperlink()
{
if (mMaiAtkHyperlink) {
MAI_ATK_HYPERLINK(mMaiAtkHyperlink)->maiHyperlink = nullptr;
g_object_unref(mMaiAtkHyperlink);
}
}
/* static functions for ATK callbacks */
void
classInitCB(AtkHyperlinkClass *aClass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS(aClass);
parent_class = g_type_class_peek_parent(aClass);
aClass->get_uri = getUriCB;
aClass->get_object = getObjectCB;
aClass->get_end_index = getEndIndexCB;
aClass->get_start_index = getStartIndexCB;
aClass->is_valid = isValidCB;
aClass->get_n_anchors = getAnchorCountCB;
gobject_class->finalize = finalizeCB;
}
void
finalizeCB(GObject *aObj)
{
NS_ASSERTION(MAI_IS_ATK_HYPERLINK(aObj), "Invalid MaiAtkHyperlink");
if (!MAI_IS_ATK_HYPERLINK(aObj))
return;
MaiAtkHyperlink *maiAtkHyperlink = MAI_ATK_HYPERLINK(aObj);
maiAtkHyperlink->maiHyperlink = nullptr;
/* call parent finalize function */
if (G_OBJECT_CLASS (parent_class)->finalize)
G_OBJECT_CLASS (parent_class)->finalize(aObj);
}
gchar *
getUriCB(AtkHyperlink *aLink, gint aLinkIndex)
{
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
if (!maiLink)
return nullptr;
nsAutoCString cautoStr;
if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
nsCOMPtr<nsIURI> uri = hyperlink->AnchorURIAt(aLinkIndex);
if (!uri)
return nullptr;
nsresult rv = uri->GetSpec(cautoStr);
NS_ENSURE_SUCCESS(rv, nullptr);
return g_strdup(cautoStr.get());
}
bool valid;
maiLink->Proxy()->AnchorURIAt(aLinkIndex, cautoStr, &valid);
if (!valid)
return nullptr;
return g_strdup(cautoStr.get());
}
AtkObject *
getObjectCB(AtkHyperlink *aLink, gint aLinkIndex)
{
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
if (!maiLink) {
return nullptr;
}
if (Accessible* hyperlink = maiLink->GetAccHyperlink()) {
Accessible* anchor = hyperlink->AnchorAt(aLinkIndex);
NS_ENSURE_TRUE(anchor, nullptr);
return AccessibleWrap::GetAtkObject(anchor);
}
ProxyAccessible* anchor = maiLink->Proxy()->AnchorAt(aLinkIndex);
return anchor ? GetWrapperFor(anchor) : nullptr;
}
gint
getEndIndexCB(AtkHyperlink *aLink)
{
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
if (!maiLink)
return false;
if (Accessible* hyperlink = maiLink->GetAccHyperlink())
return static_cast<gint>(hyperlink->EndOffset());
bool valid = false;
uint32_t endIdx = maiLink->Proxy()->EndOffset(&valid);
return valid ? static_cast<gint>(endIdx) : -1;
}
gint
getStartIndexCB(AtkHyperlink *aLink)
{
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
if (!maiLink)
return -1;
if (Accessible* hyperlink = maiLink->GetAccHyperlink())
return static_cast<gint>(hyperlink->StartOffset());
bool valid = false;
uint32_t startIdx = maiLink->Proxy()->StartOffset(&valid);
return valid ? static_cast<gint>(startIdx) : -1;
}
gboolean
isValidCB(AtkHyperlink *aLink)
{
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
if (!maiLink)
return false;
if (Accessible* hyperlink = maiLink->GetAccHyperlink())
return static_cast<gboolean>(hyperlink->IsLinkValid());
return static_cast<gboolean>(maiLink->Proxy()->IsLinkValid());
}
gint
getAnchorCountCB(AtkHyperlink *aLink)
{
MaiHyperlink* maiLink = GetMaiHyperlink(aLink);
if (!maiLink)
return -1;
if (Accessible* hyperlink = maiLink->GetAccHyperlink())
return static_cast<gint>(hyperlink->AnchorCount());
bool valid = false;
uint32_t anchorCount = maiLink->Proxy()->AnchorCount(&valid);
return valid ? static_cast<gint>(anchorCount) : -1;
}

View file

@ -0,0 +1,55 @@
/* -*- 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 __MAI_HYPERLINK_H__
#define __MAI_HYPERLINK_H__
#include "nsMai.h"
#include "Accessible.h"
struct _AtkHyperlink;
typedef struct _AtkHyperlink AtkHyperlink;
namespace mozilla {
namespace a11y {
/*
* MaiHyperlink is a auxiliary class for MaiInterfaceHyperText.
*/
class MaiHyperlink
{
public:
explicit MaiHyperlink(AccessibleOrProxy aHyperLink);
~MaiHyperlink();
public:
AtkHyperlink* GetAtkHyperlink() const { return mMaiAtkHyperlink; }
Accessible* GetAccHyperlink()
{
if (!mHyperlink.IsAccessible())
return nullptr;
Accessible* link = mHyperlink.AsAccessible();
if (!link) {
return nullptr;
}
NS_ASSERTION(link->IsLink(), "Why isn't it a link!");
return link;
}
ProxyAccessible* Proxy() const { return mHyperlink.AsProxy(); }
protected:
AccessibleOrProxy mHyperlink;
AtkHyperlink* mMaiAtkHyperlink;
};
} // namespace a11y
} // namespace mozilla
#endif /* __MAI_HYPERLINK_H__ */

View file

@ -0,0 +1,105 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "Accessible-inl.h"
#include "nsMai.h"
#include "Role.h"
#include "mozilla/Likely.h"
#include "ProxyAccessible.h"
#include "nsString.h"
using namespace mozilla::a11y;
extern "C" {
static gboolean
doActionCB(AtkAction *aAction, gint aActionIndex)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aAction));
if (accWrap) {
return accWrap->DoAction(aActionIndex);
}
ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction));
return proxy && proxy->DoAction(aActionIndex);
}
static gint
getActionCountCB(AtkAction *aAction)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aAction));
if (accWrap) {
return accWrap->ActionCount();
}
ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction));
return proxy ? proxy->ActionCount() : 0;
}
static const gchar*
getActionDescriptionCB(AtkAction *aAction, gint aActionIndex)
{
nsAutoString description;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aAction));
if (accWrap) {
accWrap->ActionDescriptionAt(aActionIndex, description);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction))) {
proxy->ActionDescriptionAt(aActionIndex, description);
} else {
return nullptr;
}
return AccessibleWrap::ReturnString(description);
}
static const gchar*
getActionNameCB(AtkAction *aAction, gint aActionIndex)
{
nsAutoString autoStr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aAction));
if (accWrap) {
accWrap->ActionNameAt(aActionIndex, autoStr);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction))) {
proxy->ActionNameAt(aActionIndex, autoStr);
} else {
return nullptr;
}
return AccessibleWrap::ReturnString(autoStr);
}
static const gchar*
getKeyBindingCB(AtkAction *aAction, gint aActionIndex)
{
nsAutoString keyBindingsStr;
AccessibleWrap* acc = GetAccessibleWrap(ATK_OBJECT(aAction));
if (acc) {
AccessibleWrap::GetKeyBinding(acc, keyBindingsStr);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aAction))) {
proxy->AtkKeyBinding(keyBindingsStr);
} else {
return nullptr;
}
return AccessibleWrap::ReturnString(keyBindingsStr);
}
}
void
actionInterfaceInitCB(AtkActionIface* aIface)
{
NS_ASSERTION(aIface, "Invalid aIface");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->do_action = doActionCB;
aIface->get_n_actions = getActionCountCB;
aIface->get_description = getActionDescriptionCB;
aIface->get_keybinding = getKeyBindingCB;
aIface->get_name = getActionNameCB;
}

View file

@ -0,0 +1,151 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "AccessibleWrap.h"
#include "nsAccUtils.h"
#include "nsCoreUtils.h"
#include "nsMai.h"
#include "mozilla/Likely.h"
#include "mozilla/a11y/ProxyAccessible.h"
using namespace mozilla::a11y;
extern "C" {
static AtkObject*
refAccessibleAtPointCB(AtkComponent* aComponent, gint aAccX, gint aAccY,
AtkCoordType aCoordType)
{
return refAccessibleAtPointHelper(ATK_OBJECT(aComponent),
aAccX, aAccY, aCoordType);
}
static void
getExtentsCB(AtkComponent* aComponent, gint* aX, gint* aY,
gint* aWidth, gint* aHeight, AtkCoordType aCoordType)
{
getExtentsHelper(ATK_OBJECT(aComponent),
aX, aY, aWidth, aHeight, aCoordType);
}
static gboolean
grabFocusCB(AtkComponent* aComponent)
{
AtkObject* atkObject = ATK_OBJECT(aComponent);
AccessibleWrap* accWrap = GetAccessibleWrap(atkObject);
if (accWrap) {
accWrap->TakeFocus();
return TRUE;
}
ProxyAccessible* proxy = GetProxy(atkObject);
if (proxy) {
proxy->TakeFocus();
return TRUE;
}
return FALSE;
}
}
AtkObject*
refAccessibleAtPointHelper(AtkObject* aAtkObj, gint aX, gint aY,
AtkCoordType aCoordType)
{
AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
if (accWrap) {
if (accWrap->IsDefunct() || nsAccUtils::MustPrune(accWrap)) {
return nullptr;
}
// Accessible::ChildAtPoint(x,y) is in screen pixels.
if (aCoordType == ATK_XY_WINDOW) {
nsIntPoint winCoords =
nsCoreUtils::GetScreenCoordsForWindow(accWrap->GetNode());
aX += winCoords.x;
aY += winCoords.y;
}
Accessible* accAtPoint = accWrap->ChildAtPoint(aX, aY,
Accessible::eDirectChild);
if (!accAtPoint) {
return nullptr;
}
AtkObject* atkObj = AccessibleWrap::GetAtkObject(accAtPoint);
if (atkObj) {
g_object_ref(atkObj);
}
return atkObj;
}
if (ProxyAccessible* proxy = GetProxy(aAtkObj)) {
ProxyAccessible* result =
proxy->AccessibleAtPoint(aX, aY, aCoordType == ATK_XY_WINDOW);
AtkObject* atkObj = result ? GetWrapperFor(result) : nullptr;
if (atkObj) {
g_object_ref(atkObj);
}
return atkObj;
}
return nullptr;
}
void
getExtentsHelper(AtkObject* aAtkObj,
gint* aX, gint* aY, gint* aWidth, gint* aHeight,
AtkCoordType aCoordType)
{
AccessibleWrap* accWrap = GetAccessibleWrap(aAtkObj);
*aX = *aY = *aWidth = *aHeight = 0;
if (accWrap) {
if (accWrap->IsDefunct()) {
return;
}
nsIntRect screenRect = accWrap->Bounds();
if (screenRect.IsEmpty())
return;
if (aCoordType == ATK_XY_WINDOW) {
nsIntPoint winCoords =
nsCoreUtils::GetScreenCoordsForWindow(accWrap->GetNode());
screenRect.x -= winCoords.x;
screenRect.y -= winCoords.y;
}
*aX = screenRect.x;
*aY = screenRect.y;
*aWidth = screenRect.width;
*aHeight = screenRect.height;
return;
}
if (ProxyAccessible* proxy = GetProxy(aAtkObj)) {
proxy->Extents(aCoordType == ATK_XY_WINDOW, aX, aY, aWidth, aHeight);
}
}
void
componentInterfaceInitCB(AtkComponentIface* aIface)
{
NS_ASSERTION(aIface, "Invalid Interface");
if(MOZ_UNLIKELY(!aIface))
return;
/*
* Use default implementation in atk for contains, get_position,
* and get_size
*/
aIface->ref_accessible_at_point = refAccessibleAtPointCB;
aIface->get_extents = getExtentsCB;
aIface->grab_focus = grabFocusCB;
}

View file

@ -0,0 +1,151 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "Accessible-inl.h"
#include "AccessibleWrap.h"
#include "DocAccessible.h"
#include "nsMai.h"
#include "ProxyAccessible.h"
#include "mozilla/Likely.h"
using namespace mozilla::a11y;
static const char* const kDocTypeName = "W3C-doctype";
static const char* const kDocUrlName = "DocURL";
static const char* const kMimeTypeName = "MimeType";
// below functions are vfuncs on an ATK interface so they need to be C call
extern "C" {
static const gchar* getDocumentLocaleCB(AtkDocument* aDocument);
static AtkAttributeSet* getDocumentAttributesCB(AtkDocument* aDocument);
static const gchar* getDocumentAttributeValueCB(AtkDocument* aDocument,
const gchar* aAttrName);
void
documentInterfaceInitCB(AtkDocumentIface *aIface)
{
NS_ASSERTION(aIface, "Invalid Interface");
if(MOZ_UNLIKELY(!aIface))
return;
/*
* We don't support get_document or set_attribute right now.
* get_document_type is deprecated, we return DocType in
* get_document_attribute_value and get_document_attributes instead.
*/
aIface->get_document_attributes = getDocumentAttributesCB;
aIface->get_document_attribute_value = getDocumentAttributeValueCB;
aIface->get_document_locale = getDocumentLocaleCB;
}
const gchar *
getDocumentLocaleCB(AtkDocument *aDocument)
{
nsAutoString locale;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
if (accWrap) {
accWrap->Language(locale);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aDocument))) {
proxy->Language(locale);
}
return locale.IsEmpty() ? nullptr : AccessibleWrap::ReturnString(locale);
}
static inline GSList *
prependToList(GSList *aList, const char *const aName, const nsAutoString &aValue)
{
if (aValue.IsEmpty()) {
return aList;
}
// libspi will free these
AtkAttribute *atkAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
atkAttr->name = g_strdup(aName);
atkAttr->value = g_strdup(NS_ConvertUTF16toUTF8(aValue).get());
return g_slist_prepend(aList, atkAttr);
}
AtkAttributeSet *
getDocumentAttributesCB(AtkDocument *aDocument)
{
nsAutoString url;
nsAutoString w3cDocType;
nsAutoString mimeType;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
if (accWrap) {
if (!accWrap->IsDoc()) {
return nullptr;
}
DocAccessible* document = accWrap->AsDoc();
document->URL(url);
document->DocType(w3cDocType);
document->MimeType(mimeType);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aDocument))) {
proxy->URLDocTypeMimeType(url, w3cDocType, mimeType);
} else {
return nullptr;
}
// according to atkobject.h, AtkAttributeSet is a GSList
GSList* attributes = nullptr;
attributes = prependToList(attributes, kDocUrlName, url);
attributes = prependToList(attributes, kDocTypeName, w3cDocType);
attributes = prependToList(attributes, kMimeTypeName, mimeType);
return attributes;
}
const gchar *
getDocumentAttributeValueCB(AtkDocument *aDocument,
const gchar *aAttrName)
{
ProxyAccessible* proxy = nullptr;
DocAccessible* document = nullptr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aDocument));
if (accWrap) {
if (!accWrap->IsDoc()) {
return nullptr;
}
document = accWrap->AsDoc();
} else {
proxy = GetProxy(ATK_OBJECT(aDocument));
if (!proxy) {
return nullptr;
}
}
nsAutoString attrValue;
if (!strcasecmp(aAttrName, kDocTypeName)) {
if (document) {
document->DocType(attrValue);
} else {
proxy->DocType(attrValue);
}
} else if (!strcasecmp(aAttrName, kDocUrlName)) {
if (document) {
document->URL(attrValue);
} else {
proxy->URL(attrValue);
}
} else if (!strcasecmp(aAttrName, kMimeTypeName)) {
if (document) {
document->MimeType(attrValue);
} else {
proxy->MimeType(attrValue);
}
} else {
return nullptr;
}
return attrValue.IsEmpty() ? nullptr : AccessibleWrap::ReturnString(attrValue);
}
}

View file

@ -0,0 +1,134 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "Accessible-inl.h"
#include "HyperTextAccessible-inl.h"
#include "nsMai.h"
#include "ProxyAccessible.h"
#include "nsString.h"
#include "mozilla/Likely.h"
using namespace mozilla::a11y;
extern "C" {
static void
setTextContentsCB(AtkEditableText *aText, const gchar *aString)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return;
}
NS_ConvertUTF8toUTF16 strContent(aString);
text->ReplaceText(strContent);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
NS_ConvertUTF8toUTF16 strContent(aString);
proxy->ReplaceText(strContent);
}
}
static void
insertTextCB(AtkEditableText *aText,
const gchar *aString, gint aLength, gint *aPosition)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return;
}
NS_ConvertUTF8toUTF16 strContent(aString);
text->InsertText(strContent, *aPosition);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
NS_ConvertUTF8toUTF16 strContent(aString);
proxy->InsertText(strContent, *aPosition);
}
}
static void
copyTextCB(AtkEditableText *aText, gint aStartPos, gint aEndPos)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return;
}
text->CopyText(aStartPos, aEndPos);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->CopyText(aStartPos, aEndPos);
}
}
static void
cutTextCB(AtkEditableText *aText, gint aStartPos, gint aEndPos)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return;
}
text->CutText(aStartPos, aEndPos);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->CutText(aStartPos, aEndPos);
}
}
static void
deleteTextCB(AtkEditableText *aText, gint aStartPos, gint aEndPos)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return;
}
text->DeleteText(aStartPos, aEndPos);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->DeleteText(aStartPos, aEndPos);
}
}
static void
pasteTextCB(AtkEditableText *aText, gint aPosition)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return;
}
text->PasteText(aPosition);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->PasteText(aPosition);
}
}
}
void
editableTextInterfaceInitCB(AtkEditableTextIface* aIface)
{
NS_ASSERTION(aIface, "Invalid aIface");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->set_text_contents = setTextContentsCB;
aIface->insert_text = insertTextCB;
aIface->copy_text = copyTextCB;
aIface->cut_text = cutTextCB;
aIface->delete_text = deleteTextCB;
aIface->paste_text = pasteTextCB;
}

View file

@ -0,0 +1,37 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "nsMaiHyperlink.h"
#include "mozilla/Likely.h"
using namespace mozilla::a11y;
extern "C" {
static AtkHyperlink*
getHyperlinkCB(AtkHyperlinkImpl* aImpl)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImpl));
if (!accWrap && !GetProxy(ATK_OBJECT(aImpl)))
return nullptr;
if (accWrap)
NS_ASSERTION(accWrap->IsLink(), "why isn't it a link!");
return MAI_ATK_OBJECT(aImpl)->GetAtkHyperlink();
}
}
void
hyperlinkImplInterfaceInitCB(AtkHyperlinkImplIface *aIface)
{
NS_ASSERTION(aIface, "no interface!");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->get_hyperlink = getHyperlinkCB;
}

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/. */
#include "InterfaceInitFuncs.h"
#include "Accessible-inl.h"
#include "HyperTextAccessible.h"
#include "nsMai.h"
#include "nsMaiHyperlink.h"
#include "ProxyAccessible.h"
#include "mozilla/Likely.h"
using namespace mozilla::a11y;
extern "C" {
static AtkHyperlink*
getLinkCB(AtkHypertext *aText, gint aLinkIndex)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
AtkObject* atkHyperLink = nullptr;
if (accWrap) {
HyperTextAccessible* hyperText = accWrap->AsHyperText();
NS_ENSURE_TRUE(hyperText, nullptr);
Accessible* hyperLink = hyperText->LinkAt(aLinkIndex);
if (!hyperLink || !hyperLink->IsLink()) {
return nullptr;
}
atkHyperLink = AccessibleWrap::GetAtkObject(hyperLink);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
ProxyAccessible* proxyLink = proxy->LinkAt(aLinkIndex);
if (!proxyLink)
return nullptr;
atkHyperLink = GetWrapperFor(proxyLink);
}
NS_ENSURE_TRUE(IS_MAI_OBJECT(atkHyperLink), nullptr);
return MAI_ATK_OBJECT(atkHyperLink)->GetAtkHyperlink();
}
static gint
getLinkCountCB(AtkHypertext *aText)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* hyperText = accWrap->AsHyperText();
NS_ENSURE_TRUE(hyperText, -1);
return hyperText->LinkCount();
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return proxy->LinkCount();
}
return -1;
}
static gint
getLinkIndexCB(AtkHypertext *aText, gint aCharIndex)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* hyperText = accWrap->AsHyperText();
NS_ENSURE_TRUE(hyperText, -1);
return hyperText->LinkIndexAtOffset(aCharIndex);
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return proxy->LinkIndexAtOffset(aCharIndex);
}
return -1;
}
}
void
hypertextInterfaceInitCB(AtkHypertextIface* aIface)
{
NS_ASSERTION(aIface, "no interface!");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->get_link = getLinkCB;
aIface->get_n_links = getLinkCountCB;
aIface->get_link_index = getLinkIndexCB;
}

View file

@ -0,0 +1,77 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "AccessibleWrap.h"
#include "ImageAccessible.h"
#include "mozilla/Likely.h"
#include "nsMai.h"
#include "nsIAccessibleTypes.h"
#include "nsIURI.h"
#include "ProxyAccessible.h"
using namespace mozilla;
using namespace mozilla::a11y;
extern "C" {
const gchar* getDescriptionCB(AtkObject* aAtkObj);
static void
getImagePositionCB(AtkImage* aImage, gint* aAccX, gint* aAccY,
AtkCoordType aCoordType)
{
nsIntPoint pos;
uint32_t geckoCoordType = (aCoordType == ATK_XY_WINDOW) ?
nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE :
nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImage));
if (accWrap && accWrap->IsImage()) {
ImageAccessible* image = accWrap->AsImage();
pos = image->Position(geckoCoordType);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aImage))) {
pos = proxy->ImagePosition(geckoCoordType);
}
*aAccX = pos.x;
*aAccY = pos.y;
}
static const gchar*
getImageDescriptionCB(AtkImage* aImage)
{
return getDescriptionCB(ATK_OBJECT(aImage));
}
static void
getImageSizeCB(AtkImage* aImage, gint* aAccWidth, gint* aAccHeight)
{
nsIntSize size;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aImage));
if (accWrap && accWrap->IsImage()) {
size = accWrap->AsImage()->Size();
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aImage))) {
size = proxy->ImageSize();
}
*aAccWidth = size.width;
*aAccHeight = size.height;
}
} // extern "C"
void
imageInterfaceInitCB(AtkImageIface* aIface)
{
NS_ASSERTION(aIface, "no interface!");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->get_image_position = getImagePositionCB;
aIface->get_image_description = getImageDescriptionCB;
aIface->get_image_size = getImageSizeCB;
}

View file

@ -0,0 +1,152 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "Accessible-inl.h"
#include "AccessibleWrap.h"
#include "nsMai.h"
#include "ProxyAccessible.h"
#include "mozilla/Likely.h"
#include <atk/atk.h>
using namespace mozilla::a11y;
extern "C" {
static gboolean
addSelectionCB(AtkSelection *aSelection, gint i)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
if (accWrap && accWrap->IsSelect()) {
return accWrap->AddItemToSelection(i);
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
return proxy->AddItemToSelection(i);
}
return FALSE;
}
static gboolean
clearSelectionCB(AtkSelection *aSelection)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
if (accWrap && accWrap->IsSelect()) {
return accWrap->UnselectAll();
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
return proxy->UnselectAll();
}
return FALSE;
}
static AtkObject*
refSelectionCB(AtkSelection *aSelection, gint i)
{
AtkObject* atkObj = nullptr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
if (accWrap && accWrap->IsSelect()) {
Accessible* selectedItem = accWrap->GetSelectedItem(i);
if (!selectedItem) {
return nullptr;
}
atkObj = AccessibleWrap::GetAtkObject(selectedItem);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
ProxyAccessible* selectedItem = proxy->GetSelectedItem(i);
if (selectedItem) {
atkObj = GetWrapperFor(selectedItem);
}
}
if (atkObj) {
g_object_ref(atkObj);
}
return atkObj;
}
static gint
getSelectionCountCB(AtkSelection *aSelection)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
if (accWrap && accWrap->IsSelect()) {
return accWrap->SelectedItemCount();
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
return proxy->SelectedItemCount();
}
return -1;
}
static gboolean
isChildSelectedCB(AtkSelection *aSelection, gint i)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
if (accWrap && accWrap->IsSelect()) {
return accWrap->IsItemSelected(i);
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
return proxy->IsItemSelected(i);
}
return FALSE;
}
static gboolean
removeSelectionCB(AtkSelection *aSelection, gint i)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
if (accWrap && accWrap->IsSelect()) {
return accWrap->RemoveItemFromSelection(i);
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
return proxy->RemoveItemFromSelection(i);
}
return FALSE;
}
static gboolean
selectAllSelectionCB(AtkSelection *aSelection)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aSelection));
if (accWrap && accWrap->IsSelect()) {
return accWrap->SelectAll();
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aSelection))) {
return proxy->SelectAll();
}
return FALSE;
}
}
void
selectionInterfaceInitCB(AtkSelectionIface* aIface)
{
NS_ASSERTION(aIface, "Invalid aIface");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->add_selection = addSelectionCB;
aIface->clear_selection = clearSelectionCB;
aIface->ref_selection = refSelectionCB;
aIface->get_selection_count = getSelectionCountCB;
aIface->is_child_selected = isChildSelectedCB;
aIface->remove_selection = removeSelectionCB;
aIface->select_all_selection = selectAllSelectionCB;
}

View file

@ -0,0 +1,391 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "Accessible-inl.h"
#include "AccessibleWrap.h"
#include "nsAccUtils.h"
#include "TableAccessible.h"
#include "TableCellAccessible.h"
#include "nsMai.h"
#include "ProxyAccessible.h"
#include "nsArrayUtils.h"
#include "mozilla/Likely.h"
using namespace mozilla::a11y;
extern "C" {
static AtkObject*
refAtCB(AtkTable* aTable, gint aRowIdx, gint aColIdx)
{
if (aRowIdx < 0 || aColIdx < 0) {
return nullptr;
}
AtkObject* cellAtkObj = nullptr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
Accessible* cell = accWrap->AsTable()->CellAt(aRowIdx, aColIdx);
if (!cell) {
return nullptr;
}
cellAtkObj = AccessibleWrap::GetAtkObject(cell);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
ProxyAccessible* cell = proxy->TableCellAt(aRowIdx, aColIdx);
if (!cell) {
return nullptr;
}
cellAtkObj = GetWrapperFor(cell);
}
if (cellAtkObj) {
g_object_ref(cellAtkObj);
}
return cellAtkObj;
}
static gint
getIndexAtCB(AtkTable* aTable, gint aRowIdx, gint aColIdx)
{
if (aRowIdx < 0 || aColIdx < 0) {
return -1;
}
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gint>(accWrap->AsTable()->CellIndexAt(aRowIdx, aColIdx));
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gint>(proxy->TableCellIndexAt(aRowIdx, aColIdx));
}
return -1;
}
static gint
getColumnAtIndexCB(AtkTable *aTable, gint aIdx)
{
if (aIdx < 0) {
return -1;
}
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gint>(accWrap->AsTable()->ColIndexAt(aIdx));
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gint>(proxy->TableColumnIndexAt(aIdx));
}
return -1;
}
static gint
getRowAtIndexCB(AtkTable *aTable, gint aIdx)
{
if (aIdx < 0) {
return -1;
}
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gint>(accWrap->AsTable()->RowIndexAt(aIdx));
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gint>(proxy->TableRowIndexAt(aIdx));
}
return -1;
}
static gint
getColumnCountCB(AtkTable *aTable)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gint>(accWrap->AsTable()->ColCount());
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gint>(proxy->TableColumnCount());
}
return -1;
}
static gint
getRowCountCB(AtkTable *aTable)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gint>(accWrap->AsTable()->RowCount());
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gint>(proxy->TableRowCount());
}
return -1;
}
static gint
getColumnExtentAtCB(AtkTable *aTable, gint aRowIdx, gint aColIdx)
{
if (aRowIdx < 0 || aColIdx < 0) {
return -1;
}
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gint>(accWrap->AsTable()->ColExtentAt(aRowIdx, aColIdx));
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gint>(proxy->TableColumnExtentAt(aRowIdx, aColIdx));
}
return -1;
}
static gint
getRowExtentAtCB(AtkTable *aTable, gint aRowIdx, gint aColIdx)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gint>(accWrap->AsTable()->RowExtentAt(aRowIdx, aColIdx));
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gint>(proxy->TableRowExtentAt(aRowIdx, aColIdx));
}
return -1;
}
static AtkObject*
getCaptionCB(AtkTable* aTable)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
Accessible* caption = accWrap->AsTable()->Caption();
return caption ? AccessibleWrap::GetAtkObject(caption) : nullptr;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
ProxyAccessible* caption = proxy->TableCaption();
return caption ? GetWrapperFor(caption) : nullptr;
}
return nullptr;
}
static const gchar*
getColumnDescriptionCB(AtkTable *aTable, gint aColumn)
{
nsAutoString autoStr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
accWrap->AsTable()->ColDescription(aColumn, autoStr);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
proxy->TableColumnDescription(aColumn, autoStr);
} else {
return nullptr;
}
return AccessibleWrap::ReturnString(autoStr);
}
static AtkObject*
getColumnHeaderCB(AtkTable *aTable, gint aColIdx)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
Accessible* header =
AccessibleWrap::GetColumnHeader(accWrap->AsTable(), aColIdx);
return header ? AccessibleWrap::GetAtkObject(header) : nullptr;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
ProxyAccessible* header = proxy->AtkTableColumnHeader(aColIdx);
return header ? GetWrapperFor(header) : nullptr;
}
return nullptr;
}
static const gchar*
getRowDescriptionCB(AtkTable *aTable, gint aRow)
{
nsAutoString autoStr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
accWrap->AsTable()->RowDescription(aRow, autoStr);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
proxy->TableRowDescription(aRow, autoStr);
} else {
return nullptr;
}
return AccessibleWrap::ReturnString(autoStr);
}
static AtkObject*
getRowHeaderCB(AtkTable *aTable, gint aRowIdx)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
Accessible* header =
AccessibleWrap::GetRowHeader(accWrap->AsTable(), aRowIdx);
return header ? AccessibleWrap::GetAtkObject(header) : nullptr;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
ProxyAccessible* header = proxy->AtkTableRowHeader(aRowIdx);
return header ? GetWrapperFor(header) : nullptr;
}
return nullptr;
}
static AtkObject*
getSummaryCB(AtkTable *aTable)
{
// Neither html:table nor xul:tree nor ARIA grid/tree have an ability to
// link an accessible object to specify a summary. There is closes method
// in TableAccessible::summary to get a summary as a string which is not
// mapped directly to ATK.
return nullptr;
}
static gint
getSelectedColumnsCB(AtkTable *aTable, gint** aSelected)
{
*aSelected = nullptr;
AutoTArray<uint32_t, 10> cols;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
accWrap->AsTable()->SelectedColIndices(&cols);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
proxy->TableSelectedColumnIndices(&cols);
} else {
return 0;
}
if (cols.IsEmpty())
return 0;
gint* atkColumns = g_new(gint, cols.Length());
if (!atkColumns) {
NS_WARNING("OUT OF MEMORY");
return 0;
}
memcpy(atkColumns, cols.Elements(), cols.Length() * sizeof(uint32_t));
*aSelected = atkColumns;
return cols.Length();
}
static gint
getSelectedRowsCB(AtkTable *aTable, gint **aSelected)
{
AutoTArray<uint32_t, 10> rows;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
accWrap->AsTable()->SelectedRowIndices(&rows);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
proxy->TableSelectedRowIndices(&rows);
} else {
return 0;
}
gint* atkRows = g_new(gint, rows.Length());
if (!atkRows) {
NS_WARNING("OUT OF MEMORY");
return 0;
}
memcpy(atkRows, rows.Elements(), rows.Length() * sizeof(uint32_t));
*aSelected = atkRows;
return rows.Length();
}
static gboolean
isColumnSelectedCB(AtkTable *aTable, gint aColIdx)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gboolean>(accWrap->AsTable()->IsColSelected(aColIdx));
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gboolean>(proxy->TableColumnSelected(aColIdx));
}
return FALSE;
}
static gboolean
isRowSelectedCB(AtkTable *aTable, gint aRowIdx)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gboolean>(accWrap->AsTable()->IsRowSelected(aRowIdx));
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gboolean>(proxy->TableRowSelected(aRowIdx));
}
return FALSE;
}
static gboolean
isCellSelectedCB(AtkTable *aTable, gint aRowIdx, gint aColIdx)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTable));
if (accWrap) {
return static_cast<gboolean>(accWrap->AsTable()->
IsCellSelected(aRowIdx, aColIdx));
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTable))) {
return static_cast<gboolean>(proxy->TableCellSelected(aRowIdx, aColIdx));
}
return FALSE;
}
}
void
tableInterfaceInitCB(AtkTableIface* aIface)
{
NS_ASSERTION(aIface, "no interface!");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->ref_at = refAtCB;
aIface->get_index_at = getIndexAtCB;
aIface->get_column_at_index = getColumnAtIndexCB;
aIface->get_row_at_index = getRowAtIndexCB;
aIface->get_n_columns = getColumnCountCB;
aIface->get_n_rows = getRowCountCB;
aIface->get_column_extent_at = getColumnExtentAtCB;
aIface->get_row_extent_at = getRowExtentAtCB;
aIface->get_caption = getCaptionCB;
aIface->get_column_description = getColumnDescriptionCB;
aIface->get_column_header = getColumnHeaderCB;
aIface->get_row_description = getRowDescriptionCB;
aIface->get_row_header = getRowHeaderCB;
aIface->get_summary = getSummaryCB;
aIface->get_selected_columns = getSelectedColumnsCB;
aIface->get_selected_rows = getSelectedRowsCB;
aIface->is_column_selected = isColumnSelectedCB;
aIface->is_row_selected = isRowSelectedCB;
aIface->is_selected = isCellSelectedCB;
}

View file

@ -0,0 +1,216 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "Accessible-inl.h"
#include "AccessibleWrap.h"
#include "nsAccUtils.h"
#include "TableAccessible.h"
#include "TableCellAccessible.h"
#include "nsMai.h"
#include "ProxyAccessible.h"
#include "nsArrayUtils.h"
#include "mozilla/Likely.h"
using namespace mozilla::a11y;
extern "C" {
static gint
GetColumnSpanCB(AtkTableCell* aCell)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell));
if (accWrap) {
return accWrap->AsTableCell()->ColExtent();
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
return proxy->ColExtent();
}
return 0;
}
static gboolean
GetRowSpanCB(AtkTableCell* aCell)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell));
if (accWrap) {
return accWrap->AsTableCell()->RowExtent();
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
return proxy->RowExtent();
}
return 0;
}
static gboolean
GetPositionCB(AtkTableCell* aCell, gint* aRow, gint* aCol)
{
if (AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell))) {
TableCellAccessible* cell = accWrap->AsTableCell();
*aRow = cell->RowIdx();
*aCol = cell->ColIdx();
return true;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
uint32_t rowIdx = 0, colIdx = 0;
proxy->GetPosition(&rowIdx, &colIdx);
*aCol = colIdx;
*aRow = rowIdx;
return true;
}
return false;
}
static gboolean
GetColumnRowSpanCB(AtkTableCell* aCell, gint* aCol, gint* aRow,
gint* aColExtent, gint* aRowExtent) {
if (AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell))) {
TableCellAccessible* cellAcc = accWrap->AsTableCell();
*aCol = cellAcc->ColIdx();
*aRow = cellAcc->RowIdx();
*aColExtent = cellAcc->ColExtent();
*aRowExtent = cellAcc->ColExtent();
return true;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
uint32_t colIdx = 0, rowIdx = 0, colExtent = 0, rowExtent = 0;
proxy->GetColRowExtents(&colIdx, &rowIdx, &colExtent, &rowExtent);
*aCol = colIdx;
*aRow = rowIdx;
*aColExtent = colExtent;
*aRowExtent = rowExtent;
return true;
}
return false;
}
static AtkObject*
GetTableCB(AtkTableCell* aTableCell)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aTableCell));
if (accWrap) {
TableAccessible* table = accWrap->AsTableCell()->Table();
if (!table) {
return nullptr;
}
Accessible* tableAcc = table->AsAccessible();
return tableAcc ? AccessibleWrap::GetAtkObject(tableAcc) : nullptr;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aTableCell))) {
ProxyAccessible* table = proxy->TableOfACell();
return table ? GetWrapperFor(table) : nullptr;
}
return nullptr;
}
static GPtrArray*
GetColumnHeaderCellsCB(AtkTableCell* aCell)
{
if (AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell))) {
AutoTArray<Accessible*, 10> headers;
accWrap->AsTableCell()->ColHeaderCells(&headers);
if (headers.IsEmpty()) {
return nullptr;
}
GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length());
for (Accessible* header: headers) {
AtkObject* atkHeader = AccessibleWrap::GetAtkObject(header);
g_object_ref(atkHeader);
g_ptr_array_add(atkHeaders, atkHeader);
}
return atkHeaders;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
AutoTArray<ProxyAccessible*, 10> headers;
proxy->ColHeaderCells(&headers);
if (headers.IsEmpty()) {
return nullptr;
}
GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length());
for (ProxyAccessible* header: headers) {
AtkObject* atkHeader = GetWrapperFor(header);
g_object_ref(atkHeader);
g_ptr_array_add(atkHeaders, atkHeader);
}
return atkHeaders;
}
return nullptr;
}
static GPtrArray*
GetRowHeaderCellsCB(AtkTableCell* aCell)
{
if (AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aCell))) {
AutoTArray<Accessible*, 10> headers;
accWrap->AsTableCell()->RowHeaderCells(&headers);
if (headers.IsEmpty()) {
return nullptr;
}
GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length());
for (Accessible* header: headers) {
AtkObject* atkHeader = AccessibleWrap::GetAtkObject(header);
g_object_ref(atkHeader);
g_ptr_array_add(atkHeaders, atkHeader);
}
return atkHeaders;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aCell))) {
AutoTArray<ProxyAccessible*, 10> headers;
proxy->RowHeaderCells(&headers);
if (headers.IsEmpty()) {
return nullptr;
}
GPtrArray* atkHeaders = g_ptr_array_sized_new(headers.Length());
for (ProxyAccessible* header: headers) {
AtkObject* atkHeader = GetWrapperFor(header);
g_object_ref(atkHeader);
g_ptr_array_add(atkHeaders, atkHeader);
}
return atkHeaders;
}
return nullptr;
}
}
void
tableCellInterfaceInitCB(AtkTableCellIface* aIface)
{
NS_ASSERTION(aIface, "no interface!");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->get_column_span = GetColumnSpanCB;
aIface->get_column_header_cells = GetColumnHeaderCellsCB;
aIface->get_position = GetPositionCB;
aIface->get_row_span = GetRowSpanCB;
aIface->get_row_header_cells = GetRowHeaderCellsCB;
aIface->get_row_column_span = GetColumnRowSpanCB;
aIface->get_table = GetTableCB;
}

View file

@ -0,0 +1,629 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "mozilla/a11y/PDocAccessible.h"
#include "Accessible-inl.h"
#include "HyperTextAccessible-inl.h"
#include "nsMai.h"
#include "ProxyAccessible.h"
#include "nsIAccessibleTypes.h"
#include "nsIPersistentProperties2.h"
#include "nsISimpleEnumerator.h"
#include "mozilla/Likely.h"
using namespace mozilla;
using namespace mozilla::a11y;
static const char* sAtkTextAttrNames[ATK_TEXT_ATTR_LAST_DEFINED];
void
ConvertTextAttributeToAtkAttribute(const nsACString& aName,
const nsAString& aValue,
AtkAttributeSet** aAttributeSet)
{
// Handle attributes where atk has its own name.
const char* atkName = nullptr;
nsAutoString atkValue;
if (aName.EqualsLiteral("color")) {
// The format of the atk attribute is r,g,b and the gecko one is
// rgb(r, g, b).
atkValue = Substring(aValue, 4, aValue.Length() - 5);
atkValue.StripWhitespace();
atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_FG_COLOR];
} else if (aName.EqualsLiteral("background-color")) {
// The format of the atk attribute is r,g,b and the gecko one is
// rgb(r, g, b).
atkValue = Substring(aValue, 4, aValue.Length() - 5);
atkValue.StripWhitespace();
atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_BG_COLOR];
} else if (aName.EqualsLiteral("font-family")) {
atkValue = aValue;
atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_FAMILY_NAME];
} else if (aName.EqualsLiteral("font-size")) {
// ATK wants the number of pixels without px at the end.
atkValue = StringHead(aValue, aValue.Length() - 2);
atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_SIZE];
} else if (aName.EqualsLiteral("font-weight")) {
atkValue = aValue;
atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_WEIGHT];
} else if (aName.EqualsLiteral("invalid")) {
atkValue = aValue;
atkName = sAtkTextAttrNames[ATK_TEXT_ATTR_INVALID];
}
if (atkName) {
AtkAttribute* objAttr =
static_cast<AtkAttribute*>(g_malloc(sizeof(AtkAttribute)));
objAttr->name = g_strdup(atkName);
objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(atkValue).get());
*aAttributeSet = g_slist_prepend(*aAttributeSet, objAttr);
}
}
static AtkAttributeSet*
ConvertToAtkTextAttributeSet(nsTArray<Attribute>& aAttributes)
{
AtkAttributeSet* objAttributeSet = nullptr;
for (size_t i = 0; i < aAttributes.Length(); ++i) {
AtkAttribute* objAttr = (AtkAttribute *)g_malloc(sizeof(AtkAttribute));
objAttr->name = g_strdup(aAttributes[i].Name().get());
objAttr->value =
g_strdup(NS_ConvertUTF16toUTF8(aAttributes[i].Value()).get());
objAttributeSet = g_slist_prepend(objAttributeSet, objAttr);
ConvertTextAttributeToAtkAttribute(aAttributes[i].Name(),
aAttributes[i].Value(),
&objAttributeSet);
}
return objAttributeSet;
}
static AtkAttributeSet*
ConvertToAtkTextAttributeSet(nsIPersistentProperties* aAttributes)
{
if (!aAttributes)
return nullptr;
AtkAttributeSet* objAttributeSet = nullptr;
nsCOMPtr<nsISimpleEnumerator> propEnum;
nsresult rv = aAttributes->Enumerate(getter_AddRefs(propEnum));
NS_ENSURE_SUCCESS(rv, nullptr);
bool hasMore = false;
while (NS_SUCCEEDED(propEnum->HasMoreElements(&hasMore)) && hasMore) {
nsCOMPtr<nsISupports> sup;
rv = propEnum->GetNext(getter_AddRefs(sup));
NS_ENSURE_SUCCESS(rv, objAttributeSet);
nsCOMPtr<nsIPropertyElement> propElem(do_QueryInterface(sup));
NS_ENSURE_TRUE(propElem, objAttributeSet);
nsAutoCString name;
rv = propElem->GetKey(name);
NS_ENSURE_SUCCESS(rv, objAttributeSet);
nsAutoString value;
rv = propElem->GetValue(value);
NS_ENSURE_SUCCESS(rv, objAttributeSet);
AtkAttribute* objAttr = (AtkAttribute*)g_malloc(sizeof(AtkAttribute));
objAttr->name = g_strdup(name.get());
objAttr->value = g_strdup(NS_ConvertUTF16toUTF8(value).get());
objAttributeSet = g_slist_prepend(objAttributeSet, objAttr);
ConvertTextAttributeToAtkAttribute(name, value, &objAttributeSet);
}
// libatk-adaptor will free it
return objAttributeSet;
}
static void
ConvertTexttoAsterisks(AccessibleWrap* accWrap, nsAString& aString)
{
// convert each char to "*" when it's "password text"
if (accWrap->NativeRole() == roles::PASSWORD_TEXT) {
for (uint32_t i = 0; i < aString.Length(); i++)
aString.Replace(i, 1, NS_LITERAL_STRING("*"));
}
}
extern "C" {
static gchar*
getTextCB(AtkText *aText, gint aStartOffset, gint aEndOffset)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
nsAutoString autoStr;
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole())
return nullptr;
text->TextSubstring(aStartOffset, aEndOffset, autoStr);
ConvertTexttoAsterisks(accWrap, autoStr);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->TextSubstring(aStartOffset, aEndOffset, autoStr);
}
NS_ConvertUTF16toUTF8 cautoStr(autoStr);
//copy and return, libspi will free it.
return (cautoStr.get()) ? g_strdup(cautoStr.get()) : nullptr;
}
static gchar*
getTextAfterOffsetCB(AtkText *aText, gint aOffset,
AtkTextBoundary aBoundaryType,
gint *aStartOffset, gint *aEndOffset)
{
nsAutoString autoStr;
int32_t startOffset = 0, endOffset = 0;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole())
return nullptr;
text->TextAfterOffset(aOffset, aBoundaryType, &startOffset, &endOffset, autoStr);
ConvertTexttoAsterisks(accWrap, autoStr);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->GetTextAfterOffset(aOffset, aBoundaryType, autoStr, &startOffset,
&endOffset);
}
*aStartOffset = startOffset;
*aEndOffset = endOffset;
NS_ConvertUTF16toUTF8 cautoStr(autoStr);
return (cautoStr.get()) ? g_strdup(cautoStr.get()) : nullptr;
}
static gchar*
getTextAtOffsetCB(AtkText *aText, gint aOffset,
AtkTextBoundary aBoundaryType,
gint *aStartOffset, gint *aEndOffset)
{
nsAutoString autoStr;
int32_t startOffset = 0, endOffset = 0;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole())
return nullptr;
text->TextAtOffset(aOffset, aBoundaryType, &startOffset, &endOffset, autoStr);
ConvertTexttoAsterisks(accWrap, autoStr);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->GetTextAtOffset(aOffset, aBoundaryType, autoStr, &startOffset,
&endOffset);
}
*aStartOffset = startOffset;
*aEndOffset = endOffset;
NS_ConvertUTF16toUTF8 cautoStr(autoStr);
return (cautoStr.get()) ? g_strdup(cautoStr.get()) : nullptr;
}
static gunichar
getCharacterAtOffsetCB(AtkText* aText, gint aOffset)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return 0;
}
// char16_t is unsigned short in Mozilla, gnuichar is guint32 in glib.
return static_cast<gunichar>(text->CharAt(aOffset));
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return static_cast<gunichar>(proxy->CharAt(aOffset));
}
return 0;
}
static gchar*
getTextBeforeOffsetCB(AtkText *aText, gint aOffset,
AtkTextBoundary aBoundaryType,
gint *aStartOffset, gint *aEndOffset)
{
nsAutoString autoStr;
int32_t startOffset = 0, endOffset = 0;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole())
return nullptr;
text->TextBeforeOffset(aOffset, aBoundaryType,
&startOffset, &endOffset, autoStr);
ConvertTexttoAsterisks(accWrap, autoStr);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->GetTextBeforeOffset(aOffset, aBoundaryType, autoStr, &startOffset,
&endOffset);
}
*aStartOffset = startOffset;
*aEndOffset = endOffset;
NS_ConvertUTF16toUTF8 cautoStr(autoStr);
return (cautoStr.get()) ? g_strdup(cautoStr.get()) : nullptr;
}
static gint
getCaretOffsetCB(AtkText *aText)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return 0;
}
return static_cast<gint>(text->CaretOffset());
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return static_cast<gint>(proxy->CaretOffset());
}
return 0;
}
static AtkAttributeSet*
getRunAttributesCB(AtkText *aText, gint aOffset,
gint *aStartOffset,
gint *aEndOffset)
{
*aStartOffset = -1;
*aEndOffset = -1;
int32_t startOffset = 0, endOffset = 0;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return nullptr;
}
nsCOMPtr<nsIPersistentProperties> attributes =
text->TextAttributes(false, aOffset, &startOffset, &endOffset);
*aStartOffset = startOffset;
*aEndOffset = endOffset;
return ConvertToAtkTextAttributeSet(attributes);
}
ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText));
if (!proxy) {
return nullptr;
}
AutoTArray<Attribute, 10> attrs;
proxy->TextAttributes(false, aOffset, &attrs, &startOffset, &endOffset);
*aStartOffset = startOffset;
*aEndOffset = endOffset;
return ConvertToAtkTextAttributeSet(attrs);
}
static AtkAttributeSet*
getDefaultAttributesCB(AtkText *aText)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return nullptr;
}
nsCOMPtr<nsIPersistentProperties> attributes = text->DefaultTextAttributes();
return ConvertToAtkTextAttributeSet(attributes);
}
ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText));
if (!proxy) {
return nullptr;
}
AutoTArray<Attribute, 10> attrs;
proxy->DefaultTextAttributes(&attrs);
return ConvertToAtkTextAttributeSet(attrs);
}
static void
getCharacterExtentsCB(AtkText *aText, gint aOffset,
gint *aX, gint *aY,
gint *aWidth, gint *aHeight,
AtkCoordType aCoords)
{
if(!aX || !aY || !aWidth || !aHeight) {
return;
}
nsIntRect rect;
uint32_t geckoCoordType;
if (aCoords == ATK_XY_SCREEN) {
geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE;
} else {
geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE;
}
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return;
}
rect = text->CharBounds(aOffset, geckoCoordType);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
rect = proxy->CharBounds(aOffset, geckoCoordType);
} else {
return;
}
*aX = rect.x;
*aY = rect.y;
*aWidth = rect.width;
*aHeight = rect.height;
}
static void
getRangeExtentsCB(AtkText *aText, gint aStartOffset, gint aEndOffset,
AtkCoordType aCoords, AtkTextRectangle *aRect)
{
if (!aRect) {
return;
}
nsIntRect rect;
uint32_t geckoCoordType;
if (aCoords == ATK_XY_SCREEN) {
geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE;
} else {
geckoCoordType = nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE;
}
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if(accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return;
}
rect = text->TextBounds(aStartOffset, aEndOffset, geckoCoordType);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
rect = proxy->TextBounds(aStartOffset, aEndOffset, geckoCoordType);
} else {
return;
}
aRect->x = rect.x;
aRect->y = rect.y;
aRect->width = rect.width;
aRect->height = rect.height;
}
static gint
getCharacterCountCB(AtkText *aText)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* textAcc = accWrap->AsHyperText();
return
textAcc->IsDefunct() ? 0 : static_cast<gint>(textAcc->CharacterCount());
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return proxy->CharacterCount();
}
return 0;
}
static gint
getOffsetAtPointCB(AtkText *aText,
gint aX, gint aY,
AtkCoordType aCoords)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return -1;
}
return static_cast<gint>(
text->OffsetAtPoint(aX, aY,
(aCoords == ATK_XY_SCREEN ?
nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE)));
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return static_cast<gint>(
proxy->OffsetAtPoint(aX, aY,
(aCoords == ATK_XY_SCREEN ?
nsIAccessibleCoordinateType::COORDTYPE_SCREEN_RELATIVE :
nsIAccessibleCoordinateType::COORDTYPE_WINDOW_RELATIVE)));
}
return -1;
}
static gint
getTextSelectionCountCB(AtkText *aText)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return 0;
}
return text->SelectionCount();
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return proxy->SelectionCount();
}
return 0;
}
static gchar*
getTextSelectionCB(AtkText *aText, gint aSelectionNum,
gint *aStartOffset, gint *aEndOffset)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
int32_t startOffset = 0, endOffset = 0;
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return nullptr;
}
text->SelectionBoundsAt(aSelectionNum, &startOffset, &endOffset);
*aStartOffset = startOffset;
*aEndOffset = endOffset;
return getTextCB(aText, *aStartOffset, *aEndOffset);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
nsString data;
proxy->SelectionBoundsAt(aSelectionNum, data, &startOffset, &endOffset);
*aStartOffset = startOffset;
*aEndOffset = endOffset;
NS_ConvertUTF16toUTF8 dataAsUTF8(data);
return (dataAsUTF8.get()) ? g_strdup(dataAsUTF8.get()) : nullptr;
}
return nullptr;
}
// set methods
static gboolean
addTextSelectionCB(AtkText *aText,
gint aStartOffset,
gint aEndOffset)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return FALSE;
}
return text->AddToSelection(aStartOffset, aEndOffset);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return proxy->AddToSelection(aStartOffset, aEndOffset);
}
return FALSE;
}
static gboolean
removeTextSelectionCB(AtkText *aText,
gint aSelectionNum)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return FALSE;
}
return text->RemoveFromSelection(aSelectionNum);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return proxy->RemoveFromSelection(aSelectionNum);
}
return FALSE;
}
static gboolean
setTextSelectionCB(AtkText *aText, gint aSelectionNum,
gint aStartOffset, gint aEndOffset)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole()) {
return FALSE;
}
return text->SetSelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
} else if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
return proxy->SetSelectionBoundsAt(aSelectionNum, aStartOffset, aEndOffset);
}
return FALSE;
}
static gboolean
setCaretOffsetCB(AtkText *aText, gint aOffset)
{
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(aText));
if (accWrap) {
HyperTextAccessible* text = accWrap->AsHyperText();
if (!text || !text->IsTextRole() || !text->IsValidOffset(aOffset)) {
return FALSE;
}
text->SetCaretOffset(aOffset);
return TRUE;
}
if (ProxyAccessible* proxy = GetProxy(ATK_OBJECT(aText))) {
proxy->SetCaretOffset(aOffset);
return TRUE;
}
return FALSE;
}
}
void
textInterfaceInitCB(AtkTextIface* aIface)
{
NS_ASSERTION(aIface, "Invalid aIface");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->get_text = getTextCB;
aIface->get_text_after_offset = getTextAfterOffsetCB;
aIface->get_text_at_offset = getTextAtOffsetCB;
aIface->get_character_at_offset = getCharacterAtOffsetCB;
aIface->get_text_before_offset = getTextBeforeOffsetCB;
aIface->get_caret_offset = getCaretOffsetCB;
aIface->get_run_attributes = getRunAttributesCB;
aIface->get_default_attributes = getDefaultAttributesCB;
aIface->get_character_extents = getCharacterExtentsCB;
aIface->get_range_extents = getRangeExtentsCB;
aIface->get_character_count = getCharacterCountCB;
aIface->get_offset_at_point = getOffsetAtPointCB;
aIface->get_n_selections = getTextSelectionCountCB;
aIface->get_selection = getTextSelectionCB;
// set methods
aIface->add_selection = addTextSelectionCB;
aIface->remove_selection = removeTextSelectionCB;
aIface->set_selection = setTextSelectionCB;
aIface->set_caret_offset = setCaretOffsetCB;
// Cache the string values of the atk text attribute names.
for (uint32_t i = 0; i < ArrayLength(sAtkTextAttrNames); i++)
sAtkTextAttrNames[i] =
atk_text_attribute_get_name(static_cast<AtkTextAttribute>(i));
}

View file

@ -0,0 +1,133 @@
/* -*- 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 "InterfaceInitFuncs.h"
#include "AccessibleWrap.h"
#include "nsMai.h"
#include "ProxyAccessible.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Likely.h"
using namespace mozilla;
using namespace mozilla::a11y;
extern "C" {
static void
getCurrentValueCB(AtkValue *obj, GValue *value)
{
ProxyAccessible* proxy = nullptr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
if (!accWrap) {
proxy = GetProxy(ATK_OBJECT(obj));
if (!proxy) {
return;
}
}
memset (value, 0, sizeof (GValue));
double accValue = accWrap ? accWrap->CurValue() : proxy->CurValue();
if (IsNaN(accValue))
return;
g_value_init (value, G_TYPE_DOUBLE);
g_value_set_double (value, accValue);
}
static void
getMaximumValueCB(AtkValue *obj, GValue *value)
{
ProxyAccessible* proxy = nullptr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
if (!accWrap) {
proxy = GetProxy(ATK_OBJECT(obj));
if (!proxy) {
return;
}
}
memset(value, 0, sizeof (GValue));
double accValue = accWrap ? accWrap->MaxValue() : proxy->MaxValue();
if (IsNaN(accValue))
return;
g_value_init(value, G_TYPE_DOUBLE);
g_value_set_double(value, accValue);
}
static void
getMinimumValueCB(AtkValue *obj, GValue *value)
{
ProxyAccessible* proxy = nullptr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
if (!accWrap) {
proxy = GetProxy(ATK_OBJECT(obj));
if (!proxy) {
return;
}
}
memset(value, 0, sizeof (GValue));
double accValue = accWrap ? accWrap->MinValue() : proxy->MinValue();
if (IsNaN(accValue))
return;
g_value_init(value, G_TYPE_DOUBLE);
g_value_set_double(value, accValue);
}
static void
getMinimumIncrementCB(AtkValue *obj, GValue *minimumIncrement)
{
ProxyAccessible* proxy = nullptr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
if (!accWrap) {
proxy = GetProxy(ATK_OBJECT(obj));
if (!proxy) {
return;
}
}
memset(minimumIncrement, 0, sizeof (GValue));
double accValue = accWrap ? accWrap->Step() : proxy->Step();
if (IsNaN(accValue))
accValue = 0; // zero if the minimum increment is undefined
g_value_init(minimumIncrement, G_TYPE_DOUBLE);
g_value_set_double(minimumIncrement, accValue);
}
static gboolean
setCurrentValueCB(AtkValue *obj, const GValue *value)
{
ProxyAccessible* proxy = nullptr;
AccessibleWrap* accWrap = GetAccessibleWrap(ATK_OBJECT(obj));
if (!accWrap) {
proxy = GetProxy(ATK_OBJECT(obj));
if (!proxy) {
return FALSE;
}
}
double accValue =g_value_get_double(value);
return accWrap ? accWrap->SetCurValue(accValue) : proxy->SetCurValue(accValue);
}
}
void
valueInterfaceInitCB(AtkValueIface* aIface)
{
NS_ASSERTION(aIface, "Invalid aIface");
if (MOZ_UNLIKELY(!aIface))
return;
aIface->get_current_value = getCurrentValueCB;
aIface->get_maximum_value = getMaximumValueCB;
aIface->get_minimum_value = getMinimumValueCB;
aIface->get_minimum_increment = getMinimumIncrementCB;
aIface->set_current_value = setCurrentValueCB;
}

117
accessible/atk/nsStateMap.h Normal file
View file

@ -0,0 +1,117 @@
/* -*- 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 <atk/atk.h>
#include "AccessibleWrap.h"
/******************************************************************************
The following accessible states aren't translated, just ignored:
STATE_READONLY: Supported indirectly via EXT_STATE_EDITABLE
STATE_HOTTRACKED: No ATK equivalent. No known use case.
The nsIAccessible state is not currently supported.
STATE_FLOATING: No ATK equivalent. No known use case.
The nsIAccessible state is not currently supported.
STATE_MOVEABLE: No ATK equivalent. No known use case.
The nsIAccessible state is not currently supported.
STATE_SELFVOICING: No ATK equivalent -- the object has self-TTS.
The nsIAccessible state is not currently supported.
STATE_LINKED: The object is formatted as a hyperlink. Supported via ATK_ROLE_LINK.
STATE_EXTSELECTABLE: Indicates that an object extends its selection.
This is supported via STATE_MULTISELECTABLE.
STATE_PROTECTED: The object is a password-protected edit control.
Supported via ATK_ROLE_PASSWORD_TEXT
STATE_HASPOPUP: Object displays a pop-up menu or window when invoked.
No ATK equivalent. The accessible state is not
currently supported.
STATE_PINNED: The object is pinned, usually indicating it is fixed in
place and has permanence. No ATK equivalent. The
accessible state is not currently supported.
The following ATK states are not supported:
ATK_STATE_ARMED: No clear use case, used briefly when button is activated
ATK_STATE_HAS_TOOLTIP: No clear use case, no IA2 equivalent
ATK_STATE_ICONIFIED: Mozilla does not have elements which are collapsable into icons
ATK_STATE_TRUNCATED: No clear use case. Indicates that an object's onscreen content is truncated,
e.g. a text value in a spreadsheet cell. No IA2 state.
******************************************************************************/
enum EStateMapEntryType {
kMapDirectly,
kMapOpposite, // For example, UNAVAILABLE is the opposite of ENABLED
kNoStateChange, // Don't fire state change event
kNoSuchState
};
const AtkStateType kNone = ATK_STATE_INVALID;
struct AtkStateMap {
AtkStateType atkState;
EStateMapEntryType stateMapEntryType;
static int32_t GetStateIndexFor(uint64_t aState)
{
int32_t stateIndex = -1;
while (aState > 0) {
++ stateIndex;
aState >>= 1;
}
return stateIndex; // Returns -1 if not mapped
}
};
// Map array from cross platform states to ATK states
static const AtkStateMap gAtkStateMap[] = { // Cross Platform States
{ kNone, kMapOpposite }, // states::UNAVAILABLE = 1 << 0
{ ATK_STATE_SELECTED, kMapDirectly }, // states::SELECTED = 1 << 1
{ ATK_STATE_FOCUSED, kMapDirectly }, // states::FOCUSED = 1 << 2
{ ATK_STATE_PRESSED, kMapDirectly }, // states::PRESSED = 1 << 3
{ ATK_STATE_CHECKED, kMapDirectly }, // states::CHECKED = 1 << 4
{ ATK_STATE_INDETERMINATE, kMapDirectly }, // states::MIXED = 1 << 5
{ kNone, kMapDirectly }, // states::READONLY = 1 << 6
{ kNone, kMapDirectly }, // states::HOTTRACKED = 1 << 7
{ ATK_STATE_DEFAULT, kMapDirectly }, // states::DEFAULT = 1 << 8
{ ATK_STATE_EXPANDED, kMapDirectly }, // states::EXPANDED = 1 << 9
{ kNone, kNoStateChange }, // states::COLLAPSED = 1 << 10
{ ATK_STATE_BUSY, kMapDirectly }, // states::BUSY = 1 << 11
{ kNone, kMapDirectly }, // states::FLOATING = 1 << 12
{ ATK_STATE_CHECKABLE, kMapDirectly }, // states::CHECKABLE = 1 << 13
{ ATK_STATE_ANIMATED, kMapDirectly }, // states::ANIMATED = 1 << 14
{ ATK_STATE_VISIBLE, kMapOpposite }, // states::INVISIBLE = 1 << 15
{ ATK_STATE_SHOWING, kMapOpposite }, // states::OFFSCREEN = 1 << 16
{ ATK_STATE_RESIZABLE, kMapDirectly }, // states::SIZEABLE = 1 << 17
{ kNone, kMapDirectly }, // states::MOVEABLE = 1 << 18
{ kNone, kMapDirectly }, // states::SELFVOICING = 1 << 19
{ ATK_STATE_FOCUSABLE, kMapDirectly }, // states::FOCUSABLE = 1 << 20
{ ATK_STATE_SELECTABLE, kMapDirectly }, // states::SELECTABLE = 1 << 21
{ kNone, kMapDirectly }, // states::LINKED = 1 << 22
{ ATK_STATE_VISITED, kMapDirectly }, // states::TRAVERSED = 1 << 23
{ ATK_STATE_MULTISELECTABLE, kMapDirectly }, // states::MULTISELECTABLE = 1 << 24
{ kNone, kMapDirectly }, // states::EXTSELECTABLE = 1 << 25
{ ATK_STATE_REQUIRED, kMapDirectly }, // states::STATE_REQUIRED = 1 << 26
{ kNone, kMapDirectly }, // states::ALERT_MEDIUM = 1 << 27
{ ATK_STATE_INVALID_ENTRY, kMapDirectly }, // states::INVALID = 1 << 28
{ kNone, kMapDirectly }, // states::PROTECTED = 1 << 29
{ kNone, kMapDirectly }, // states::HASPOPUP = 1 << 30
{ ATK_STATE_SUPPORTS_AUTOCOMPLETION, kMapDirectly }, // states::SUPPORTS_AUTOCOMPLETION = 1 << 31
{ ATK_STATE_DEFUNCT, kMapDirectly }, // states::DEFUNCT = 1 << 32
{ ATK_STATE_SELECTABLE_TEXT, kMapDirectly }, // states::SELECTABLE_TEXT = 1 << 33
{ ATK_STATE_EDITABLE, kMapDirectly }, // states::EDITABLE = 1 << 34
{ ATK_STATE_ACTIVE, kMapDirectly }, // states::ACTIVE = 1 << 35
{ ATK_STATE_MODAL, kMapDirectly }, // states::MODAL = 1 << 36
{ ATK_STATE_MULTI_LINE, kMapDirectly }, // states::MULTI_LINE = 1 << 37
{ ATK_STATE_HORIZONTAL, kMapDirectly }, // states::HORIZONTAL = 1 << 38
{ ATK_STATE_OPAQUE, kMapDirectly }, // states::OPAQUE = 1 << 39
{ ATK_STATE_SINGLE_LINE, kMapDirectly }, // states::SINGLE_LINE = 1 << 40
{ ATK_STATE_TRANSIENT, kMapDirectly }, // states::TRANSIENT = 1 << 41
{ ATK_STATE_VERTICAL, kMapDirectly }, // states::VERTICAL = 1 << 42
{ ATK_STATE_STALE, kMapDirectly }, // states::STALE = 1 << 43
{ ATK_STATE_ENABLED, kMapDirectly }, // states::ENABLED = 1 << 44
{ ATK_STATE_SENSITIVE, kMapDirectly }, // states::SENSITIVE = 1 << 45
{ ATK_STATE_EXPANDABLE, kMapDirectly }, // states::EXPANDABLE = 1 << 46
{ kNone, kMapDirectly }, // states::PINNED = 1 << 47
{ kNone, kNoSuchState }, // = 1 << 48
};

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

Some files were not shown because too many files have changed in this diff Show more