зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1479037 - Introduce native event support 4/4. r=jchen,yzen?jamie
Depends on D6683 Differential Revision: https://phabricator.services.mozilla.com/D6684 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
f3d88538aa
Коммит
8102b1e502
|
@ -40,6 +40,118 @@ AccessibleWrap::AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc)
|
|||
//-----------------------------------------------------
|
||||
AccessibleWrap::~AccessibleWrap() {}
|
||||
|
||||
nsresult
|
||||
AccessibleWrap::HandleAccEvent(AccEvent* aEvent)
|
||||
{
|
||||
nsresult rv = Accessible::HandleAccEvent(aEvent);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (IPCAccessibilityActive()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
auto accessible = static_cast<AccessibleWrap*>(aEvent->GetAccessible());
|
||||
NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE);
|
||||
|
||||
// The accessible can become defunct if we have an xpcom event listener
|
||||
// which decides it would be fun to change the DOM and flush layout.
|
||||
if (accessible->IsDefunct() || !accessible->IsBoundToParent()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (DocAccessible* doc = accessible->Document()) {
|
||||
if (!nsCoreUtils::IsContentDocument(doc->DocumentNode())) {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(accessible);
|
||||
if (!sessionAcc) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
switch (aEvent->GetEventType()) {
|
||||
case nsIAccessibleEvent::EVENT_FOCUS:
|
||||
sessionAcc->SendFocusEvent(accessible);
|
||||
break;
|
||||
case nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED: {
|
||||
AccVCChangeEvent* vcEvent = downcast_accEvent(aEvent);
|
||||
auto newPosition = static_cast<AccessibleWrap*>(vcEvent->NewAccessible());
|
||||
auto oldPosition = static_cast<AccessibleWrap*>(vcEvent->OldAccessible());
|
||||
|
||||
if (sessionAcc && newPosition) {
|
||||
if (oldPosition != newPosition) {
|
||||
if (vcEvent->Reason() == nsIAccessiblePivot::REASON_POINT) {
|
||||
sessionAcc->SendHoverEnterEvent(newPosition);
|
||||
} else {
|
||||
sessionAcc->SendAccessibilityFocusedEvent(newPosition);
|
||||
}
|
||||
}
|
||||
|
||||
if (vcEvent->BoundaryType() != nsIAccessiblePivot::NO_BOUNDARY) {
|
||||
sessionAcc->SendTextTraversedEvent(
|
||||
newPosition, vcEvent->NewStartOffset(), vcEvent->NewEndOffset());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
|
||||
AccCaretMoveEvent* event = downcast_accEvent(aEvent);
|
||||
sessionAcc->SendTextSelectionChangedEvent(accessible,
|
||||
event->GetCaretOffset());
|
||||
break;
|
||||
}
|
||||
case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
|
||||
case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
|
||||
AccTextChangeEvent* event = downcast_accEvent(aEvent);
|
||||
sessionAcc->SendTextChangedEvent(accessible,
|
||||
event->ModifiedText(),
|
||||
event->GetStartOffset(),
|
||||
event->GetLength(),
|
||||
event->IsTextInserted(),
|
||||
event->IsFromUserInput());
|
||||
break;
|
||||
}
|
||||
case nsIAccessibleEvent::EVENT_STATE_CHANGE: {
|
||||
AccStateChangeEvent* event = downcast_accEvent(aEvent);
|
||||
auto state = event->GetState();
|
||||
if (state & states::CHECKED) {
|
||||
sessionAcc->SendClickedEvent(accessible);
|
||||
}
|
||||
|
||||
if (state & states::SELECTED) {
|
||||
sessionAcc->SendSelectedEvent(accessible);
|
||||
}
|
||||
|
||||
if (state & states::BUSY) {
|
||||
sessionAcc->SendWindowStateChangedEvent(accessible);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case nsIAccessibleEvent::EVENT_SCROLLING: {
|
||||
AccScrollingEvent* event = downcast_accEvent(aEvent);
|
||||
sessionAcc->SendScrollingEvent(accessible,
|
||||
event->ScrollX(),
|
||||
event->ScrollY(),
|
||||
event->MaxScrollX(),
|
||||
event->MaxScrollY());
|
||||
break;
|
||||
}
|
||||
case nsIAccessibleEvent::EVENT_SHOW:
|
||||
case nsIAccessibleEvent::EVENT_HIDE: {
|
||||
AccMutationEvent* event = downcast_accEvent(aEvent);
|
||||
auto parent = static_cast<AccessibleWrap*>(event->Parent());
|
||||
sessionAcc->SendWindowContentChangedEvent(parent);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleWrap::Shutdown()
|
||||
{
|
||||
|
@ -75,6 +187,24 @@ AccessibleWrap::SetTextContents(const nsAString& aText) {
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleWrap::GetTextContents(nsAString& aText) {
|
||||
// For now it is a simple wrapper for getting entire range of TextSubstring.
|
||||
// In the future this may be smarter and retrieve a flattened string.
|
||||
if (IsHyperText()) {
|
||||
AsHyperText()->TextSubstring(0, -1, aText);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
AccessibleWrap::GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) {
|
||||
if (IsHyperText()) {
|
||||
return AsHyperText()->SelectionBoundsAt(0, aStartOffset, aEndOffset);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
mozilla::java::GeckoBundle::LocalRef
|
||||
AccessibleWrap::CreateBundle(int32_t aParentID,
|
||||
role aRole,
|
||||
|
|
|
@ -20,12 +20,17 @@ public:
|
|||
AccessibleWrap(nsIContent* aContent, DocAccessible* aDoc);
|
||||
virtual ~AccessibleWrap();
|
||||
|
||||
virtual nsresult HandleAccEvent(AccEvent* aEvent) override;
|
||||
virtual void Shutdown() override;
|
||||
|
||||
int32_t VirtualViewID() const { return mID; }
|
||||
|
||||
virtual void SetTextContents(const nsAString& aText);
|
||||
|
||||
virtual void GetTextContents(nsAString& aText);
|
||||
|
||||
virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset);
|
||||
|
||||
virtual mozilla::java::GeckoBundle::LocalRef ToBundle();
|
||||
|
||||
static const int32_t kNoID = -1;
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
#include "Platform.h"
|
||||
#include "ProxyAccessibleWrap.h"
|
||||
#include "SessionAccessibility.h"
|
||||
#include "mozilla/a11y/ProxyAccessible.h"
|
||||
#include "nsIAccessibleEvent.h"
|
||||
#include "nsIAccessiblePivot.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::a11y;
|
||||
|
@ -54,33 +57,85 @@ a11y::ProxyDestroyed(ProxyAccessible* aProxy)
|
|||
}
|
||||
|
||||
void
|
||||
a11y::ProxyEvent(ProxyAccessible*, uint32_t)
|
||||
a11y::ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
if (!sessionAcc) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aEventType) {
|
||||
case nsIAccessibleEvent::EVENT_FOCUS:
|
||||
sessionAcc->SendFocusEvent(WrapperFor(aTarget));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
a11y::ProxyStateChangeEvent(ProxyAccessible*, uint64_t, bool)
|
||||
a11y::ProxyStateChangeEvent(ProxyAccessible* aTarget,
|
||||
uint64_t aState,
|
||||
bool aEnabled)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
|
||||
if (!sessionAcc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aState & states::CHECKED) {
|
||||
sessionAcc->SendClickedEvent(WrapperFor(aTarget));
|
||||
}
|
||||
|
||||
if (aState & states::SELECTED) {
|
||||
sessionAcc->SendSelectedEvent(WrapperFor(aTarget));
|
||||
}
|
||||
|
||||
if (aState & states::BUSY) {
|
||||
sessionAcc->SendWindowStateChangedEvent(WrapperFor(aTarget));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
a11y::ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
|
||||
if (sessionAcc) {
|
||||
sessionAcc->SendTextSelectionChangedEvent(WrapperFor(aTarget), aOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
a11y::ProxyTextChangeEvent(ProxyAccessible*,
|
||||
const nsString&,
|
||||
int32_t,
|
||||
uint32_t,
|
||||
bool,
|
||||
bool)
|
||||
a11y::ProxyTextChangeEvent(ProxyAccessible* aTarget,
|
||||
const nsString& aStr,
|
||||
int32_t aStart,
|
||||
uint32_t aLen,
|
||||
bool aIsInsert,
|
||||
bool aFromUser)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
|
||||
if (sessionAcc) {
|
||||
sessionAcc->SendTextChangedEvent(
|
||||
WrapperFor(aTarget), aStr, aStart, aLen, aIsInsert, aFromUser);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
a11y::ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
|
||||
a11y::ProxyShowHideEvent(ProxyAccessible* aTarget,
|
||||
ProxyAccessible* aParent,
|
||||
bool aInsert,
|
||||
bool aFromUser)
|
||||
{
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
if (sessionAcc) {
|
||||
sessionAcc->SendWindowContentChangedEvent(WrapperFor(aParent));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -89,25 +144,57 @@ a11y::ProxySelectionEvent(ProxyAccessible*, ProxyAccessible*, uint32_t)
|
|||
}
|
||||
|
||||
void
|
||||
a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible*,
|
||||
ProxyAccessible*,
|
||||
int32_t,
|
||||
int32_t,
|
||||
ProxyAccessible*,
|
||||
int32_t,
|
||||
int32_t,
|
||||
int16_t,
|
||||
int16_t,
|
||||
bool)
|
||||
a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible* aTarget,
|
||||
ProxyAccessible* aOldPosition,
|
||||
int32_t aOldStartOffset,
|
||||
int32_t aOldEndOffset,
|
||||
ProxyAccessible* aNewPosition,
|
||||
int32_t aNewStartOffset,
|
||||
int32_t aNewEndOffset,
|
||||
int16_t aReason,
|
||||
int16_t aBoundaryType,
|
||||
bool aFromUser)
|
||||
{
|
||||
if (!aNewPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
|
||||
if (!sessionAcc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aOldPosition != aNewPosition) {
|
||||
if (aReason == nsIAccessiblePivot::REASON_POINT) {
|
||||
sessionAcc->SendHoverEnterEvent(WrapperFor(aNewPosition));
|
||||
} else {
|
||||
sessionAcc->SendAccessibilityFocusedEvent(WrapperFor(aNewPosition));
|
||||
}
|
||||
}
|
||||
|
||||
if (aBoundaryType != nsIAccessiblePivot::NO_BOUNDARY) {
|
||||
sessionAcc->SendTextTraversedEvent(
|
||||
WrapperFor(aNewPosition), aNewStartOffset, aNewEndOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
a11y::ProxyScrollingEvent(ProxyAccessible*,
|
||||
uint32_t,
|
||||
uint32_t,
|
||||
uint32_t,
|
||||
uint32_t,
|
||||
uint32_t)
|
||||
a11y::ProxyScrollingEvent(ProxyAccessible* aTarget,
|
||||
uint32_t aEventType,
|
||||
uint32_t aScrollX,
|
||||
uint32_t aScrollY,
|
||||
uint32_t aMaxScrollX,
|
||||
uint32_t aMaxScrollY)
|
||||
{
|
||||
if (aEventType == nsIAccessibleEvent::EVENT_SCROLLING) {
|
||||
SessionAccessibility* sessionAcc =
|
||||
SessionAccessibility::GetInstanceFor(aTarget);
|
||||
|
||||
if (sessionAcc) {
|
||||
sessionAcc->SendScrollingEvent(
|
||||
WrapperFor(aTarget), aScrollX, aScrollY, aMaxScrollX, aMaxScrollY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,18 @@ ProxyAccessibleWrap::Attributes()
|
|||
return attributes.forget();
|
||||
}
|
||||
|
||||
uint32_t
|
||||
ProxyAccessibleWrap::ChildCount() const
|
||||
{
|
||||
return Proxy()->ChildrenCount();
|
||||
}
|
||||
|
||||
void
|
||||
ProxyAccessibleWrap::ScrollTo(uint32_t aHow) const
|
||||
{
|
||||
Proxy()->ScrollTo(aHow);
|
||||
}
|
||||
|
||||
// Other
|
||||
|
||||
void
|
||||
|
@ -74,6 +86,22 @@ ProxyAccessibleWrap::SetTextContents(const nsAString& aText)
|
|||
Proxy()->ReplaceText(PromiseFlatString(aText));
|
||||
}
|
||||
|
||||
void
|
||||
ProxyAccessibleWrap::GetTextContents(nsAString& aText)
|
||||
{
|
||||
nsAutoString text;
|
||||
Proxy()->TextSubstring(0, -1, text);
|
||||
aText.Assign(text);
|
||||
}
|
||||
|
||||
bool
|
||||
ProxyAccessibleWrap::GetSelectionBounds(int32_t* aStartOffset,
|
||||
int32_t* aEndOffset)
|
||||
{
|
||||
nsAutoString unused;
|
||||
return Proxy()->SelectionBoundsAt(0, unused, aStartOffset, aEndOffset);
|
||||
}
|
||||
|
||||
mozilla::java::GeckoBundle::LocalRef
|
||||
ProxyAccessibleWrap::ToBundle()
|
||||
{
|
||||
|
|
|
@ -32,10 +32,18 @@ public:
|
|||
|
||||
virtual already_AddRefed<nsIPersistentProperties> Attributes() override;
|
||||
|
||||
virtual uint32_t ChildCount() const override;
|
||||
|
||||
virtual void ScrollTo(uint32_t aHow) const override;
|
||||
|
||||
// AccessibleWrap
|
||||
|
||||
virtual void SetTextContents(const nsAString& aText) override;
|
||||
|
||||
virtual void GetTextContents(nsAString& aText) override;
|
||||
|
||||
virtual bool GetSelectionBounds(int32_t* aStartOffset, int32_t* aEndOffset) override;
|
||||
|
||||
virtual mozilla::java::GeckoBundle::LocalRef ToBundle() override;
|
||||
};
|
||||
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
#include "SessionAccessibility.h"
|
||||
#include "AndroidUiThread.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "AccessibilityEvent.h"
|
||||
#include "HyperTextAccessible.h"
|
||||
#include "JavaBuiltins.h"
|
||||
#include "RootAccessibleWrap.h"
|
||||
#include "nsAccessibilityService.h"
|
||||
#include "nsViewManager.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <android/log.h>
|
||||
|
@ -109,3 +111,209 @@ SessionAccessibility::SetText(int32_t aID, jni::String::Param aText)
|
|||
acc->SetTextContents(aText->ToString());
|
||||
}
|
||||
}
|
||||
|
||||
SessionAccessibility*
|
||||
SessionAccessibility::GetInstanceFor(ProxyAccessible* aAccessible)
|
||||
{
|
||||
Accessible* outerDoc = aAccessible->OuterDocOfRemoteBrowser();
|
||||
if (!outerDoc) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return GetInstanceFor(outerDoc);
|
||||
}
|
||||
|
||||
SessionAccessibility*
|
||||
SessionAccessibility::GetInstanceFor(Accessible* aAccessible)
|
||||
{
|
||||
RootAccessible* rootAcc = aAccessible->RootAccessible();
|
||||
nsIPresShell* shell = rootAcc->PresShell();
|
||||
nsViewManager* vm = shell->GetViewManager();
|
||||
if (!vm) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIWidget> rootWidget;
|
||||
vm->GetRootWidget(getter_AddRefs(rootWidget));
|
||||
// `rootWidget` can be one of several types. Here we make sure it is an
|
||||
// android nsWindow that implemented NS_NATIVE_WIDGET to return itself.
|
||||
if (rootWidget &&
|
||||
rootWidget->WindowType() == nsWindowType::eWindowType_toplevel &&
|
||||
rootWidget->GetNativeData(NS_NATIVE_WIDGET) == rootWidget) {
|
||||
return static_cast<nsWindow*>(rootWidget.get())->GetSessionAccessibility();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible)
|
||||
{
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_VIEW_ACCESSIBILITY_FOCUSED,
|
||||
aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
|
||||
aAccessible->ScrollTo(nsIAccessibleScrollType::SCROLL_TYPE_ANYWHERE);
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendHoverEnterEvent(AccessibleWrap* aAccessible)
|
||||
{
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_VIEW_HOVER_ENTER,
|
||||
aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendFocusEvent(AccessibleWrap* aAccessible)
|
||||
{
|
||||
// Suppress focus events from about:blank pages.
|
||||
// This is important for tests.
|
||||
if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_VIEW_FOCUSED,
|
||||
aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendScrollingEvent(AccessibleWrap* aAccessible,
|
||||
int32_t aScrollX,
|
||||
int32_t aScrollY,
|
||||
int32_t aMaxScrollX,
|
||||
int32_t aMaxScrollY)
|
||||
{
|
||||
int32_t virtualViewId = aAccessible->VirtualViewID();
|
||||
|
||||
if (virtualViewId != AccessibleWrap::kNoID) {
|
||||
// XXX: Support scrolling in subframes
|
||||
return;
|
||||
}
|
||||
|
||||
GECKOBUNDLE_START(eventInfo);
|
||||
GECKOBUNDLE_PUT(eventInfo, "scrollX", java::sdk::Integer::ValueOf(aScrollX));
|
||||
GECKOBUNDLE_PUT(eventInfo, "scrollY", java::sdk::Integer::ValueOf(aScrollY));
|
||||
GECKOBUNDLE_PUT(eventInfo, "maxScrollX", java::sdk::Integer::ValueOf(aMaxScrollX));
|
||||
GECKOBUNDLE_PUT(eventInfo, "maxScrollY", java::sdk::Integer::ValueOf(aMaxScrollY));
|
||||
GECKOBUNDLE_FINISH(eventInfo);
|
||||
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_VIEW_SCROLLED, virtualViewId,
|
||||
eventInfo, aAccessible->ToBundle());
|
||||
|
||||
SendWindowContentChangedEvent(aAccessible);
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendWindowContentChangedEvent(AccessibleWrap* aAccessible)
|
||||
{
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_WINDOW_CONTENT_CHANGED,
|
||||
aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendWindowStateChangedEvent(AccessibleWrap* aAccessible)
|
||||
{
|
||||
// Suppress window state changed events from about:blank pages.
|
||||
// This is important for tests.
|
||||
if (aAccessible->IsDoc() && aAccessible->ChildCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_WINDOW_STATE_CHANGED,
|
||||
aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
|
||||
int32_t aCaretOffset)
|
||||
{
|
||||
int32_t fromIndex = aCaretOffset;
|
||||
int32_t startSel = -1;
|
||||
int32_t endSel = -1;
|
||||
if (aAccessible->GetSelectionBounds(&startSel, &endSel)) {
|
||||
fromIndex = startSel == aCaretOffset ? endSel : startSel;
|
||||
}
|
||||
|
||||
GECKOBUNDLE_START(eventInfo);
|
||||
GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(fromIndex));
|
||||
GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aCaretOffset));
|
||||
GECKOBUNDLE_FINISH(eventInfo);
|
||||
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_SELECTION_CHANGED,
|
||||
aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendTextChangedEvent(AccessibleWrap* aAccessible,
|
||||
const nsString& aStr,
|
||||
int32_t aStart,
|
||||
uint32_t aLen,
|
||||
bool aIsInsert,
|
||||
bool aFromUser)
|
||||
{
|
||||
if (!aFromUser) {
|
||||
// Only dispatch text change events from users, for now.
|
||||
return;
|
||||
}
|
||||
|
||||
nsAutoString text;
|
||||
aAccessible->GetTextContents(text);
|
||||
nsAutoString beforeText(text);
|
||||
if (aIsInsert) {
|
||||
beforeText.Cut(aStart, aLen);
|
||||
} else {
|
||||
beforeText.Insert(aStr, aStart);
|
||||
}
|
||||
|
||||
GECKOBUNDLE_START(eventInfo);
|
||||
GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
|
||||
GECKOBUNDLE_PUT(eventInfo, "beforeText", jni::StringParam(beforeText));
|
||||
GECKOBUNDLE_PUT(eventInfo, "addedCount", java::sdk::Integer::ValueOf(aIsInsert ? aLen : 0));
|
||||
GECKOBUNDLE_PUT(eventInfo, "removedCount", java::sdk::Integer::ValueOf(aIsInsert ? 0 : aLen));
|
||||
GECKOBUNDLE_FINISH(eventInfo);
|
||||
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_VIEW_TEXT_CHANGED,
|
||||
aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendTextTraversedEvent(AccessibleWrap* aAccessible,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset)
|
||||
{
|
||||
nsAutoString text;
|
||||
aAccessible->GetTextContents(text);
|
||||
|
||||
GECKOBUNDLE_START(eventInfo);
|
||||
GECKOBUNDLE_PUT(eventInfo, "text", jni::StringParam(text));
|
||||
GECKOBUNDLE_PUT(eventInfo, "fromIndex", java::sdk::Integer::ValueOf(aStartOffset));
|
||||
GECKOBUNDLE_PUT(eventInfo, "toIndex", java::sdk::Integer::ValueOf(aEndOffset));
|
||||
GECKOBUNDLE_FINISH(eventInfo);
|
||||
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::
|
||||
TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
|
||||
aAccessible->VirtualViewID(), eventInfo, aAccessible->ToBundle());
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendClickedEvent(AccessibleWrap* aAccessible)
|
||||
{
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_VIEW_CLICKED,
|
||||
aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
|
||||
}
|
||||
|
||||
void
|
||||
SessionAccessibility::SendSelectedEvent(AccessibleWrap* aAccessible)
|
||||
{
|
||||
mSessionAccessibility->SendEvent(
|
||||
java::sdk::AccessibilityEvent::TYPE_VIEW_SELECTED,
|
||||
aAccessible->VirtualViewID(), nullptr, aAccessible->ToBundle());
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@ public:
|
|||
}
|
||||
|
||||
static void Init();
|
||||
static SessionAccessibility* GetInstanceFor(ProxyAccessible* aAccessible);
|
||||
static SessionAccessibility* GetInstanceFor(Accessible* aAccessible);
|
||||
|
||||
// Native implementations
|
||||
using Base::AttachNative;
|
||||
|
@ -77,6 +79,31 @@ public:
|
|||
void SetText(int32_t aID, jni::String::Param aText);
|
||||
void StartNativeAccessibility();
|
||||
|
||||
// Event methods
|
||||
void SendFocusEvent(AccessibleWrap* aAccessible);
|
||||
void SendScrollingEvent(AccessibleWrap* aAccessible,
|
||||
int32_t aScrollX,
|
||||
int32_t aScrollY,
|
||||
int32_t aMaxScrollX,
|
||||
int32_t aMaxScrollY);
|
||||
void SendAccessibilityFocusedEvent(AccessibleWrap* aAccessible);
|
||||
void SendHoverEnterEvent(AccessibleWrap* aAccessible);
|
||||
void SendTextSelectionChangedEvent(AccessibleWrap* aAccessible,
|
||||
int32_t aCaretOffset);
|
||||
void SendTextTraversedEvent(AccessibleWrap* aAccessible,
|
||||
int32_t aStartOffset,
|
||||
int32_t aEndOffset);
|
||||
void SendTextChangedEvent(AccessibleWrap* aAccessible,
|
||||
const nsString& aStr,
|
||||
int32_t aStart,
|
||||
uint32_t aLen,
|
||||
bool aIsInsert,
|
||||
bool aFromUser);
|
||||
void SendSelectedEvent(AccessibleWrap* aAccessible);
|
||||
void SendClickedEvent(AccessibleWrap* aAccessible);
|
||||
void SendWindowContentChangedEvent(AccessibleWrap* aAccessible);
|
||||
void SendWindowStateChangedEvent(AccessibleWrap* aAccessible);
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SessionAccessibility)
|
||||
|
||||
private:
|
||||
|
|
|
@ -555,7 +555,7 @@ public:
|
|||
/**
|
||||
* Scroll the accessible into view.
|
||||
*/
|
||||
void ScrollTo(uint32_t aHow) const;
|
||||
virtual void ScrollTo(uint32_t aHow) const;
|
||||
|
||||
/**
|
||||
* Scroll the accessible to the given point.
|
||||
|
|
|
@ -19,6 +19,7 @@ const GECKOVIEW_MESSAGE = {
|
|||
ACTIVATE: "GeckoView:AccessibilityActivate",
|
||||
BY_GRANULARITY: "GeckoView:AccessibilityByGranularity",
|
||||
CLIPBOARD: "GeckoView:AccessibilityClipboard",
|
||||
CURSOR_TO_FOCUSED: "GeckoView:AccessibilityCursorToFocused",
|
||||
EXPLORE_BY_TOUCH: "GeckoView:AccessibilityExploreByTouch",
|
||||
LONG_PRESS: "GeckoView:AccessibilityLongPress",
|
||||
NEXT: "GeckoView:AccessibilityNext",
|
||||
|
@ -176,6 +177,9 @@ var AccessFu = {
|
|||
case GECKOVIEW_MESSAGE.SCROLL_BACKWARD:
|
||||
this.Input.androidScroll("backward");
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.CURSOR_TO_FOCUSED:
|
||||
this.autoMove({ moveToFocused: true });
|
||||
break;
|
||||
case GECKOVIEW_MESSAGE.BY_GRANULARITY:
|
||||
this.Input.moveByGranularity(data);
|
||||
break;
|
||||
|
|
|
@ -12,6 +12,7 @@ import android.graphics.Rect
|
|||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
|
||||
import android.support.test.filters.MediumTest
|
||||
import android.support.test.InstrumentationRegistry
|
||||
|
@ -33,7 +34,6 @@ import org.hamcrest.Matchers.*
|
|||
import org.junit.Test
|
||||
import org.junit.Before
|
||||
import org.junit.After
|
||||
import org.junit.Ignore
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
const val DISPLAY_WIDTH = 480
|
||||
|
@ -97,6 +97,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
fun onTextChanged(event: AccessibilityEvent) { }
|
||||
fun onTextTraversal(event: AccessibilityEvent) { }
|
||||
fun onWinContentChanged(event: AccessibilityEvent) { }
|
||||
fun onWinStateChanged(event: AccessibilityEvent) { }
|
||||
}
|
||||
|
||||
@Before fun setup() {
|
||||
|
@ -126,6 +127,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> newDelegate.onTextChanged(event)
|
||||
AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> newDelegate.onTextTraversal(event)
|
||||
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> newDelegate.onWinContentChanged(event)
|
||||
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED -> newDelegate.onWinStateChanged(event)
|
||||
else -> {}
|
||||
}
|
||||
return false
|
||||
|
@ -140,11 +142,21 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
nodeInfos.forEach { node -> node.recycle() }
|
||||
}
|
||||
|
||||
private fun waitForInitialFocus() {
|
||||
private fun waitForInitialFocus(moveToFirstChild: Boolean = false) {
|
||||
// XXX: Sometimes we get the window state change of the initial
|
||||
// about:blank page loading. Need to figure out how to ignore that.
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onFocused(event: AccessibilityEvent) { }
|
||||
|
||||
@AssertCalled
|
||||
override fun onWinStateChanged(event: AccessibilityEvent) { }
|
||||
})
|
||||
|
||||
if (moveToFirstChild) {
|
||||
provider.performAction(View.NO_ID,
|
||||
AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
|
||||
}
|
||||
}
|
||||
|
||||
@Test fun testRootNode() {
|
||||
|
@ -154,7 +166,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
node.className.toString(), equalTo("android.webkit.WebView"))
|
||||
}
|
||||
|
||||
@Ignore @Test fun testPageLoad() {
|
||||
@Test fun testPageLoad() {
|
||||
sessionRule.session.loadTestPath(INPUTS_PATH)
|
||||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
|
@ -163,19 +175,18 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
})
|
||||
}
|
||||
|
||||
@Ignore @Test fun testAccessibilityFocus() {
|
||||
@Test fun testAccessibilityFocus() {
|
||||
var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
|
||||
sessionRule.session.loadTestPath(INPUTS_PATH)
|
||||
waitForInitialFocus()
|
||||
|
||||
provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
|
||||
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
|
||||
waitForInitialFocus(true)
|
||||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
val node = createNodeInfo(nodeId)
|
||||
assertThat("Label accessibility focused", node.className.toString(),
|
||||
equalTo("android.view.View"))
|
||||
assertThat("Text node should not be focusable", node.isFocusable, equalTo(false))
|
||||
}
|
||||
})
|
||||
|
@ -188,12 +199,14 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
val node = createNodeInfo(nodeId)
|
||||
assertThat("Editbox accessibility focused", node.className.toString(),
|
||||
equalTo("android.widget.EditText"))
|
||||
assertThat("Entry node should be focusable", node.isFocusable, equalTo(true))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Ignore @Test fun testTextEntryNode() {
|
||||
@Test fun testTextEntryNode() {
|
||||
sessionRule.session.loadString("<input aria-label='Name' value='Tobias'>", "text/html")
|
||||
waitForInitialFocus()
|
||||
|
||||
|
@ -201,7 +214,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
override fun onFocused(event: AccessibilityEvent) {
|
||||
val nodeId = getSourceId(event)
|
||||
val node = createNodeInfo(nodeId)
|
||||
assertThat("Focused EditBox", node.className.toString(),
|
||||
|
@ -275,7 +288,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
return arguments
|
||||
}
|
||||
|
||||
@Ignore @Test fun testClipboard() {
|
||||
@Test fun testClipboard() {
|
||||
var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
|
||||
sessionRule.session.loadString("<input value='hello cruel world' id='input'>", "text/html")
|
||||
waitForInitialFocus()
|
||||
|
@ -284,7 +297,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
override fun onFocused(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
val node = createNodeInfo(nodeId)
|
||||
assertThat("Focused EditBox", node.className.toString(),
|
||||
|
@ -326,13 +339,10 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
})
|
||||
}
|
||||
|
||||
@Ignore @Test fun testMoveByCharacter() {
|
||||
@Test fun testMoveByCharacter() {
|
||||
var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
|
||||
sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
|
||||
waitForInitialFocus()
|
||||
|
||||
provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
|
||||
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
|
||||
waitForInitialFocus(true)
|
||||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
|
@ -359,13 +369,10 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
waitUntilTextTraversed(0, 1) // "L"
|
||||
}
|
||||
|
||||
@Ignore @Test fun testMoveByWord() {
|
||||
@Test fun testMoveByWord() {
|
||||
var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
|
||||
sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
|
||||
waitForInitialFocus()
|
||||
|
||||
provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
|
||||
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
|
||||
waitForInitialFocus(true)
|
||||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
|
@ -392,10 +399,10 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
waitUntilTextTraversed(0, 5) // "Lorem"
|
||||
}
|
||||
|
||||
@Ignore @Test fun testMoveByLine() {
|
||||
@Test fun testMoveByLine() {
|
||||
var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID
|
||||
sessionRule.session.loadTestPath(LOREM_IPSUM_HTML_PATH)
|
||||
waitForInitialFocus()
|
||||
waitForInitialFocus(true)
|
||||
|
||||
provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
|
||||
AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
|
||||
|
@ -425,12 +432,11 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
waitUntilTextTraversed(0, 18) // "Lorem ipsum dolor "
|
||||
}
|
||||
|
||||
@Ignore @Test fun testCheckbox() {
|
||||
@Test fun testCheckbox() {
|
||||
var nodeId = AccessibilityNodeProvider.HOST_VIEW_ID;
|
||||
sessionRule.session.loadString("<label><input id='checkbox' type='checkbox'>many option</label>", "text/html")
|
||||
waitForInitialFocus()
|
||||
sessionRule.session.loadString("<label><input type='checkbox'>many option</label>", "text/html")
|
||||
waitForInitialFocus(true)
|
||||
|
||||
mainSession.evaluateJS("$('#checkbox').focus()")
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
|
@ -440,7 +446,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
assertThat("Checkbox node is clickable", node.isClickable, equalTo(true))
|
||||
assertThat("Checkbox node is focusable", node.isFocusable, equalTo(true))
|
||||
assertThat("Checkbox node is not checked", node.isChecked, equalTo(false))
|
||||
assertThat("Checkbox node has correct role", node.text.toString(), equalTo("many option check button"))
|
||||
assertThat("Checkbox node has correct role", node.text.toString(), equalTo("many option"))
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -451,16 +457,16 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
waitUntilClick(false)
|
||||
}
|
||||
|
||||
@Ignore @Test fun testSelectable() {
|
||||
@Test fun testSelectable() {
|
||||
var nodeId = View.NO_ID
|
||||
sessionRule.session.loadString(
|
||||
"""<ul style="list-style-type: none;" role="listbox">
|
||||
<li id="li" role="option" onclick="this.setAttribute('aria-selected',
|
||||
this.getAttribute('aria-selected') == 'true' ? 'false' : 'true')">1</li>
|
||||
<li role="option" aria-selected="false">2</li>
|
||||
</ul>""","text/html")
|
||||
waitForInitialFocus()
|
||||
waitForInitialFocus(true)
|
||||
|
||||
provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1)
|
||||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
|
@ -468,7 +474,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
var node = createNodeInfo(nodeId)
|
||||
assertThat("Selectable node is clickable", node.isClickable, equalTo(true))
|
||||
assertThat("Selectable node is not selected", node.isSelected, equalTo(false))
|
||||
assertThat("Selectable node has correct role", node.text.toString(), equalTo("1 option list box"))
|
||||
assertThat("Selectable node has correct text", node.text.toString(), equalTo("1"))
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -492,7 +498,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
return screenRect.contains(nodeBounds)
|
||||
}
|
||||
|
||||
@Ignore @Test fun testScroll() {
|
||||
@Test fun testScroll() {
|
||||
var nodeId = View.NO_ID
|
||||
sessionRule.session.loadString(
|
||||
"""<body style="margin: 0;">
|
||||
|
@ -502,9 +508,11 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||
</body>""",
|
||||
"text/html")
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled
|
||||
override fun onWinStateChanged(event: AccessibilityEvent) { }
|
||||
|
||||
@AssertCalled(count = 1)
|
||||
override fun onFocused(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
|
@ -515,7 +523,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
}
|
||||
})
|
||||
|
||||
provider.performAction(View.NO_ID, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null)
|
||||
provider.performAction(View.NO_ID, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1, order = [1])
|
||||
override fun onAccessibilityFocused(event: AccessibilityEvent) {
|
||||
|
@ -531,25 +539,25 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|
||||
@AssertCalled(count = 1, order = [3])
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
|
||||
}
|
||||
})
|
||||
|
||||
SystemClock.sleep(100);
|
||||
provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1, order = [1])
|
||||
override fun onScrolled(event: AccessibilityEvent) {
|
||||
assertThat("View is scrolled to the end", event.scrollY, equalTo(event.maxScrollY))
|
||||
assertThat("View is scrolled to the end", event.scrollY.toDouble(), closeTo(event.maxScrollY.toDouble(), 1.0))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 1, order = [2])
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
assertThat("Focused node is still onscreen", screenContainsNode(nodeId), equalTo(true))
|
||||
}
|
||||
})
|
||||
|
||||
SystemClock.sleep(100)
|
||||
provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1, order = [1])
|
||||
|
@ -559,11 +567,11 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|
||||
@AssertCalled(count = 1, order = [2])
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
assertThat("Focused node is offscreen", screenContainsNode(nodeId), equalTo(false))
|
||||
}
|
||||
})
|
||||
|
||||
SystemClock.sleep(100)
|
||||
provider.performAction(nodeId, AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, null)
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 1, order = [1])
|
||||
|
@ -574,12 +582,11 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|
||||
@AssertCalled(count = 1, order = [2])
|
||||
override fun onScrolled(event: AccessibilityEvent) {
|
||||
assertThat("View is scrolled to the end", event.scrollY, equalTo(event.maxScrollY))
|
||||
assertThat("View is scrolled to the end", event.scrollY.toDouble(), closeTo(event.maxScrollY.toDouble(), 1.0))
|
||||
}
|
||||
|
||||
@AssertCalled(count = 1, order = [3])
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) {
|
||||
nodeId = getSourceId(event)
|
||||
assertThat("Focused node is onscreen", screenContainsNode(nodeId), equalTo(true))
|
||||
}
|
||||
})
|
||||
|
@ -588,17 +595,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
@Test fun autoFill() {
|
||||
// Wait for the accessibility nodes to populate.
|
||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
||||
// sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
// // For the root document and the iframe document, each has a form group and
|
||||
// // a group for inputs outside of forms, so the total count is 4.
|
||||
// @AssertCalled(count = 4)
|
||||
// override fun onWinContentChanged(event: AccessibilityEvent) {
|
||||
// }
|
||||
// })
|
||||
// A quick but not reliable way to test the a11y tree. The next patch will have events
|
||||
// to work with..
|
||||
sessionRule.waitForPageStop()
|
||||
|
||||
waitForInitialFocus()
|
||||
|
||||
val autoFills = mapOf(
|
||||
"#user1" to "bar", "#pass1" to "baz", "#user2" to "bar", "#pass2" to "baz") +
|
||||
|
@ -668,12 +665,12 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Ignore @Test fun autoFill_navigation() {
|
||||
@Test fun autoFill_navigation() {
|
||||
fun countAutoFillNodes(cond: (AccessibilityNodeInfo) -> Boolean =
|
||||
{ it.className == "android.widget.EditText" },
|
||||
id: Int = View.NO_ID): Int {
|
||||
val info = createNodeInfo(id)
|
||||
return (if (cond(info)) 1 else 0) + (if (info.childCount > 0)
|
||||
return (if (cond(info) && info.className != "android.webkit.WebView" ) 1 else 0) + (if (info.childCount > 0)
|
||||
(0 until info.childCount).sumBy {
|
||||
countAutoFillNodes(cond, info.getChildId(it))
|
||||
} else 0)
|
||||
|
@ -681,11 +678,8 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|
||||
// Wait for the accessibility nodes to populate.
|
||||
mainSession.loadTestPath(FORMS_HTML_PATH)
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 4)
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) {
|
||||
}
|
||||
})
|
||||
waitForInitialFocus()
|
||||
|
||||
assertThat("Initial auto-fill count should match",
|
||||
countAutoFillNodes(), equalTo(14))
|
||||
assertThat("Password auto-fill count should match",
|
||||
|
@ -693,17 +687,13 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|
||||
// Now wait for the nodes to clear.
|
||||
mainSession.loadTestPath(HELLO_HTML_PATH)
|
||||
mainSession.waitForPageStop()
|
||||
waitForInitialFocus()
|
||||
assertThat("Should not have auto-fill fields",
|
||||
countAutoFillNodes(), equalTo(0))
|
||||
|
||||
// Now wait for the nodes to reappear.
|
||||
mainSession.goBack()
|
||||
sessionRule.waitUntilCalled(object : EventDelegate {
|
||||
@AssertCalled(count = 4)
|
||||
override fun onWinContentChanged(event: AccessibilityEvent) {
|
||||
}
|
||||
})
|
||||
waitForInitialFocus()
|
||||
assertThat("Should have auto-fill fields again",
|
||||
countAutoFillNodes(), equalTo(14))
|
||||
assertThat("Should not have focused field",
|
||||
|
@ -732,10 +722,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
sessionRule.session.loadString(
|
||||
"<label for='name'>Name:</label><input id='name' type='text' value='Julie'><button>Submit</button>",
|
||||
"text/html")
|
||||
// waitForInitialFocus()
|
||||
// A quick but not reliable way to test the a11y tree. The next patch will have events
|
||||
// to work with..
|
||||
sessionRule.waitForPageStop()
|
||||
waitForInitialFocus()
|
||||
|
||||
val rootNode = createNodeInfo(View.NO_ID)
|
||||
assertThat("Document has 3 children", rootNode.childCount, equalTo(3))
|
||||
|
@ -777,10 +764,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|</ul>
|
||||
""".trimMargin(),
|
||||
"text/html")
|
||||
// waitForInitialFocus()
|
||||
// A quick but not reliable way to test the a11y tree. The next patch will have events
|
||||
// to work with..
|
||||
sessionRule.waitForPageStop()
|
||||
waitForInitialFocus()
|
||||
|
||||
val rootNode = createNodeInfo(View.NO_ID)
|
||||
assertThat("Document has 2 children", rootNode.childCount, equalTo(2))
|
||||
|
@ -816,10 +800,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||
|<input type="range" aria-label="Percent" min="0" max="1" step="0.01" value="0.83">
|
||||
""".trimMargin(),
|
||||
"text/html")
|
||||
// waitForInitialFocus()
|
||||
// A quick but not reliable way to test the a11y tree. The next patch will have events
|
||||
// to work with..
|
||||
sessionRule.waitForPageStop()
|
||||
waitForInitialFocus()
|
||||
|
||||
val rootNode = createNodeInfo(View.NO_ID)
|
||||
assertThat("Document has 3 children", rootNode.childCount, equalTo(3))
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.mozilla.gecko.EventDispatcher;
|
|||
import org.mozilla.gecko.GeckoAppShell;
|
||||
import org.mozilla.gecko.PrefsHelper;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.mozglue.JNIObject;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -19,7 +20,6 @@ import android.graphics.Rect;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
@ -72,21 +72,36 @@ public class SessionAccessibility {
|
|||
}
|
||||
node.setClassName("android.webkit.WebView");
|
||||
}
|
||||
|
||||
node.setAccessibilityFocused(mAccessibilityFocusedNode == virtualDescendantId);
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAction(final int virtualViewId, int action, Bundle arguments) {
|
||||
final GeckoBundle data;
|
||||
|
||||
switch (action) {
|
||||
case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS:
|
||||
final AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
|
||||
event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
|
||||
event.setSource(mView, virtualViewId);
|
||||
((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
|
||||
if (virtualViewId == View.NO_ID) {
|
||||
sendEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, View.NO_ID, null, null);
|
||||
} else {
|
||||
final GeckoBundle nodeInfo = nativeProvider.getNodeInfo(virtualViewId);
|
||||
final int flags = nodeInfo.getInt("flags");
|
||||
if ((flags & FLAG_FOCUSED) != 0) {
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityCursorToFocused", null);
|
||||
} else {
|
||||
sendEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED, virtualViewId, null, nodeInfo);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_CLICK:
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityActivate", null);
|
||||
GeckoBundle nodeInfo = nativeProvider.getNodeInfo(virtualViewId);
|
||||
final int flags = nodeInfo.getInt("flags");
|
||||
if ((flags & (FLAG_SELECTABLE | FLAG_CHECKABLE)) == 0) {
|
||||
sendEvent(AccessibilityEvent.TYPE_VIEW_CLICKED, virtualViewId, null, nodeInfo);
|
||||
}
|
||||
return true;
|
||||
case AccessibilityNodeInfo.ACTION_LONG_CLICK:
|
||||
mSession.getEventDispatcher().dispatch("GeckoView:AccessibilityLongPress", null);
|
||||
|
@ -166,6 +181,16 @@ public class SessionAccessibility {
|
|||
return mView.performAccessibilityAction(action, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessibilityNodeInfo findFocus(int focus) {
|
||||
if (focus == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY &&
|
||||
mAccessibilityFocusedNode != 0) {
|
||||
return createAccessibilityNodeInfo(mAccessibilityFocusedNode);
|
||||
}
|
||||
|
||||
return super.findFocus(focus);
|
||||
}
|
||||
|
||||
private void populateNodeFromBundle(final AccessibilityNodeInfo node, final GeckoBundle nodeInfo) {
|
||||
if (mView == null || nodeInfo == null) {
|
||||
return;
|
||||
|
@ -364,6 +389,8 @@ public class SessionAccessibility {
|
|||
// The native portion of the node provider.
|
||||
/* package */ final NativeProvider nativeProvider = new NativeProvider();
|
||||
private boolean mAttached = false;
|
||||
// The current node with accessibility focus
|
||||
private int mAccessibilityFocusedNode = 0;
|
||||
|
||||
/* package */ SessionAccessibility(final GeckoSession session) {
|
||||
mSession = session;
|
||||
|
@ -455,6 +482,8 @@ public class SessionAccessibility {
|
|||
PrefsHelper.addObserver(new String[]{ FORCE_ACCESSIBILITY_PREF }, prefHandler);
|
||||
}
|
||||
|
||||
public static boolean isPlatformEnabled() { return sEnabled; }
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return sEnabled || sForceEnabled;
|
||||
}
|
||||
|
@ -515,6 +544,55 @@ public class SessionAccessibility {
|
|||
return true;
|
||||
}
|
||||
|
||||
/* package */ void sendEvent(final int eventType, final int sourceId, final GeckoBundle eventData, final GeckoBundle sourceInfo) {
|
||||
ThreadUtils.assertOnUiThread();
|
||||
if (mView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Settings.isPlatformEnabled() && (Build.VERSION.SDK_INT < 17 || mView.getDisplay() != null)) {
|
||||
// Accessibility could be activated in Gecko via xpcom, for example when using a11y
|
||||
// devtools. Here we assure that either Android a11y is *really* enabled, or no
|
||||
// display is attached and we must be in a junit test.
|
||||
return;
|
||||
}
|
||||
if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
|
||||
mAccessibilityFocusedNode = sourceId;
|
||||
}
|
||||
|
||||
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
|
||||
event.setPackageName(GeckoAppShell.getApplicationContext().getPackageName());
|
||||
event.setSource(mView, sourceId);
|
||||
event.setEnabled(true);
|
||||
|
||||
if (sourceInfo != null) {
|
||||
final int flags = sourceInfo.getInt("flags");
|
||||
event.setClassName(sourceInfo.getString("className", "android.view.View"));
|
||||
event.setChecked((flags & FLAG_CHECKED) != 0);
|
||||
event.getText().add(sourceInfo.getString("text", ""));
|
||||
}
|
||||
|
||||
if (eventData != null) {
|
||||
if (eventData.containsKey("text")) {
|
||||
event.getText().add(eventData.getString("text"));
|
||||
}
|
||||
event.setContentDescription(eventData.getString("description", ""));
|
||||
event.setAddedCount(eventData.getInt("addedCount", -1));
|
||||
event.setRemovedCount(eventData.getInt("removedCount", -1));
|
||||
event.setFromIndex(eventData.getInt("fromIndex", -1));
|
||||
event.setItemCount(eventData.getInt("itemCount", -1));
|
||||
event.setCurrentItemIndex(eventData.getInt("currentItemIndex", -1));
|
||||
event.setBeforeText(eventData.getString("beforeText", ""));
|
||||
event.setToIndex(eventData.getInt("toIndex", -1));
|
||||
event.setScrollX(eventData.getInt("scrollX", -1));
|
||||
event.setScrollY(eventData.getInt("scrollY", -1));
|
||||
event.setMaxScrollX(eventData.getInt("maxScrollX", -1));
|
||||
event.setMaxScrollY(eventData.getInt("maxScrollY", -1));
|
||||
}
|
||||
|
||||
((ViewParent) mView).requestSendAccessibilityEvent(mView, event);
|
||||
}
|
||||
|
||||
/* package */ final class NativeProvider extends JNIObject {
|
||||
@WrapForJNI(calledFrom = "ui")
|
||||
private void setAttached(final boolean attached) {
|
||||
|
@ -532,5 +610,15 @@ public class SessionAccessibility {
|
|||
|
||||
@WrapForJNI(dispatchTo = "gecko")
|
||||
public native void setText(int id, String text);
|
||||
|
||||
@WrapForJNI(calledFrom = "gecko", stubName = "SendEvent")
|
||||
private void sendEventNative(final int eventType, final int sourceId, final GeckoBundle eventData, final GeckoBundle sourceInfo) {
|
||||
ThreadUtils.postToUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendEvent(eventType, sourceId, eventData, sourceInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# We only use constants from KeyEvent
|
||||
[android.view.accessibility.AccessibilityEvent = skip:true]
|
||||
<field> = skip:false
|
|
@ -10,6 +10,7 @@ with Files("**"):
|
|||
# List of stems to generate .cpp and .h files for. To add a stem, add it to
|
||||
# this list and ensure that $(stem)-classes.txt exists in this directory.
|
||||
generated = [
|
||||
'AccessibilityEvent',
|
||||
'AndroidBuild',
|
||||
'AndroidRect',
|
||||
'JavaBuiltins',
|
||||
|
|
Загрузка…
Ссылка в новой задаче