Bug 1479591 - Introduced accessibility scrolling event and interface. r=surkov

This commit is contained in:
Eitan Isaacson 2018-08-14 11:46:00 +03:00
Родитель 48060b389e
Коммит 0f281891a2
21 изменённых файлов: 250 добавлений и 24 удалений

Просмотреть файл

@ -276,6 +276,13 @@ a11y::MakeXPCEvent(AccEvent* aEvent)
return xpEvent.forget();
}
if (eventGroup & (1 << AccEvent::eScrollingEvent)) {
AccScrollingEvent* sa = downcast_accEvent(aEvent);
xpEvent = new xpcAccScrollingEvent(type, ToXPC(acc), ToXPCDocument(doc), node,
fromUser, sa->ScrollX(), sa->ScrollY(),
sa->MaxScrollX(), sa->MaxScrollY());
}
xpEvent = new xpcAccEvent(type, ToXPC(acc), ToXPCDocument(doc), node, fromUser);
return xpEvent.forget();
}

Просмотреть файл

@ -104,7 +104,8 @@ public:
eSelectionChangeEvent,
eTableChangeEvent,
eVirtualCursorChangeEvent,
eObjectAttrChangedEvent
eObjectAttrChangedEvent,
eScrollingEvent,
};
static const EventGroup kEventGroup = eGenericEvent;
@ -547,6 +548,46 @@ private:
virtual ~AccObjectAttrChangedEvent() { }
};
/**
* Accessible scroll event.
*/
class AccScrollingEvent : public AccEvent
{
public:
AccScrollingEvent(uint32_t aEventType, Accessible* aAccessible,
uint32_t aScrollX, uint32_t aScrollY,
uint32_t aMaxScrollX, uint32_t aMaxScrollY) :
AccEvent(aEventType, aAccessible),
mScrollX(aScrollX),
mScrollY(aScrollY),
mMaxScrollX(aMaxScrollX),
mMaxScrollY(aMaxScrollY) { }
virtual ~AccScrollingEvent() { }
// AccEvent
static const EventGroup kEventGroup = eScrollingEvent;
virtual unsigned int GetEventGroups() const override
{
return AccEvent::GetEventGroups() | (1U << eScrollingEvent);
}
// The X scrolling offset of the container when the event was fired.
uint32_t ScrollX() { return mScrollX; }
// The Y scrolling offset of the container when the event was fired.
uint32_t ScrollY() { return mScrollY; }
// The max X offset of the container.
uint32_t MaxScrollX() { return mMaxScrollX; }
// The max Y offset of the container.
uint32_t MaxScrollY() { return mMaxScrollY; }
private:
uint32_t mScrollX;
uint32_t mScrollY;
uint32_t mMaxScrollX;
uint32_t mMaxScrollY;
};
/**
* Downcast the generic accessible event object to derived type.
*/

Просмотреть файл

@ -113,6 +113,10 @@ void ProxyVirtualCursorChangeEvent(ProxyAccessible* aTarget,
int32_t aNewEndOffset,
int16_t aReason, int16_t aBoundaryType,
bool aFromUser);
void ProxyScrollingEvent(ProxyAccessible* aTarget,
uint32_t aScrollX, uint32_t aScrollY,
uint32_t aMaxScrollX, uint32_t aMaxScrollY);
#endif
} // namespace a11y
} // namespace mozilla

Просмотреть файл

@ -473,6 +473,7 @@ static const char kEventTypeNames[][40] = {
"object attribute changed", // EVENT_OBJECT_ATTRIBUTE_CHANGED
"virtual cursor changed", // EVENT_VIRTUALCURSOR_CHANGED
"text value change", // EVENT_TEXT_VALUE_CHANGE
"scrolling", // EVENT_SCROLLING
};
#endif

Просмотреть файл

@ -952,6 +952,14 @@ Accessible::HandleAccEvent(AccEvent* aEvent)
break;
}
#endif
case nsIAccessibleEvent::EVENT_SCROLLING_END:
case nsIAccessibleEvent::EVENT_SCROLLING: {
AccScrollingEvent* scrollingEvent = downcast_accEvent(aEvent);
ipcDoc->SendScrollingEvent(id, aEvent->GetEventType(),
scrollingEvent->ScrollX(), scrollingEvent->ScrollY(),
scrollingEvent->MaxScrollX(), scrollingEvent->MaxScrollY());
break;
}
default:
ipcDoc->SendEvent(id, aEvent->GetEventType());
}

Просмотреть файл

@ -610,17 +610,10 @@ DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure)
{
DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
if (docAcc && docAcc->mScrollPositionChangedTicks &&
++docAcc->mScrollPositionChangedTicks > 2) {
// Whenever scroll position changes, mScrollPositionChangeTicks gets reset to 1
// We only want to fire accessibilty scroll event when scrolling stops or pauses
// Therefore, we wait for no scroll events to occur between 2 ticks of this timer
// That indicates a pause in scrolling, so we fire the accessibilty scroll event
nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_SCROLLING_END, docAcc);
if (docAcc) {
docAcc->DispatchScrollingEvent(nsIAccessibleEvent::EVENT_SCROLLING_END);
docAcc->mScrollPositionChangedTicks = 0;
if (docAcc->mScrollWatchTimer) {
docAcc->mScrollWatchTimer->Cancel();
docAcc->mScrollWatchTimer = nullptr;
NS_RELEASE(docAcc); // Release kung fu death grip
}
@ -633,24 +626,31 @@ DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure)
void
DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY)
{
// Start new timer, if the timer cycles at least 1 full cycle without more scroll position changes,
// then the ::Notify() method will fire the accessibility event for scroll position changes
const uint32_t kScrollPosCheckWait = 50;
const uint32_t kScrollEventInterval = 100;
TimeStamp timestamp = TimeStamp::Now();
if (mLastScrollingDispatch.IsNull() ||
(timestamp - mLastScrollingDispatch).ToMilliseconds() >= kScrollEventInterval) {
DispatchScrollingEvent(nsIAccessibleEvent::EVENT_SCROLLING);
mLastScrollingDispatch = timestamp;
}
// If timer callback is still pending, push it 100ms into the future.
// When scrolling ends and we don't fire this callback anymore, the
// timer callback will fire and dispatch an EVENT_SCROLLING_END.
if (mScrollWatchTimer) {
mScrollWatchTimer->SetDelay(kScrollPosCheckWait); // Create new timer, to avoid leaks
mScrollWatchTimer->SetDelay(kScrollEventInterval);
}
else {
NS_NewTimerWithFuncCallback(getter_AddRefs(mScrollWatchTimer),
ScrollTimerCallback,
this,
kScrollPosCheckWait,
nsITimer::TYPE_REPEATING_SLACK,
kScrollEventInterval,
nsITimer::TYPE_ONE_SHOT,
"a11y::DocAccessible::ScrollPositionDidChange");
if (mScrollWatchTimer) {
NS_ADDREF_THIS(); // Kung fu death grip
}
}
mScrollPositionChangedTicks = 1;
}
////////////////////////////////////////////////////////////////////////////////
@ -2443,3 +2443,24 @@ DocAccessible::IsLoadEventTarget() const
// It's content (not chrome) root document.
return (treeItem->ItemType() == nsIDocShellTreeItem::typeContent);
}
void
DocAccessible::DispatchScrollingEvent(uint32_t aEventType)
{
nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
int32_t appUnitsPerDevPixel = mPresShell->GetPresContext()->AppUnitsPerDevPixel();
LayoutDevicePoint scrollPoint = LayoutDevicePoint::FromAppUnits(
sf->GetScrollPosition(), appUnitsPerDevPixel) * mPresShell->GetResolution();
LayoutDeviceRect scrollRange = LayoutDeviceRect::FromAppUnits(
sf->GetScrollRange(), appUnitsPerDevPixel);
scrollRange.ScaleRoundOut(mPresShell->GetResolution());
RefPtr<AccEvent> event = new AccScrollingEvent(aEventType, this,
scrollPoint.x, scrollPoint.y,
scrollRange.width,
scrollRange.height);
nsEventShell::FireEvent(event);
}

Просмотреть файл

@ -582,6 +582,8 @@ protected:
*/
static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure);
void DispatchScrollingEvent(uint32_t aEventType);
protected:
/**
@ -605,6 +607,7 @@ protected:
nsIDocument* mDocumentNode;
nsCOMPtr<nsITimer> mScrollWatchTimer;
uint16_t mScrollPositionChangedTicks; // Used for tracking scroll events
TimeStamp mLastScrollingDispatch;
/**
* Bit mask of document load states (@see LoadState).

Просмотреть файл

@ -23,6 +23,7 @@ XPIDL_SOURCES += [
'nsIAccessiblePivot.idl',
'nsIAccessibleRelation.idl',
'nsIAccessibleRole.idl',
'nsIAccessibleScrollingEvent.idl',
'nsIAccessibleSelectable.idl',
'nsIAccessibleStateChangeEvent.idl',
'nsIAccessibleStates.idl',

Просмотреть файл

@ -418,10 +418,15 @@ interface nsIAccessibleEvent : nsISupports
*/
const unsigned long EVENT_TEXT_VALUE_CHANGE = 0x0057;
/**
* An accessible's viewport is scrolling.
*/
const unsigned long EVENT_SCROLLING = 0x0058;
/**
* Help make sure event map does not get out-of-line.
*/
const unsigned long EVENT_LAST_ENTRY = 0x0058;
const unsigned long EVENT_LAST_ENTRY = 0x0059;
/**
* The type of event, based on the enumerated event values

Просмотреть файл

@ -0,0 +1,34 @@
/* -*- 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 "nsIAccessibleEvent.idl"
/*
* An interface scroll events.
* Stores new scroll position and max scroll position.
*/
[scriptable, builtinclass, uuid(f75f0b32-5342-4d60-b1a5-b7bd6888eef5)]
interface nsIAccessibleScrollingEvent : nsIAccessibleEvent
{
/**
* New X scroll position within a scrollable container in device pixels.
*/
readonly attribute unsigned long scrollX;
/**
* New Y scroll position within a scrollable container in device pixels.
*/
readonly attribute unsigned long scrollY;
/**
* Max X scroll position within a scrollable container in device pixels.
*/
readonly attribute unsigned long maxScrollX;
/**
* Max Y scroll position within a scrollable container in device pixels.
*/
readonly attribute unsigned long maxScrollY;
};

Просмотреть файл

@ -426,6 +426,32 @@ DocAccessibleParent::RecvVirtualCursorChangeEvent(const uint64_t& aID,
return IPC_OK();
}
mozilla::ipc::IPCResult
DocAccessibleParent::RecvScrollingEvent(const uint64_t& aID,
const uint64_t& aType,
const uint32_t& aScrollX,
const uint32_t& aScrollY,
const uint32_t& aMaxScrollX,
const uint32_t& aMaxScrollY)
{
ProxyAccessible* target = GetAccessible(aID);
#if defined(ANDROID)
ProxyScrollingEvent(target, aScrollX, aScrollY, aMaxScrollX, aMaxScrollY);
#endif
xpcAccessibleGeneric* xpcAcc = GetXPCAccessible(target);
xpcAccessibleDocument* doc = GetAccService()->GetXPCDocument(this);
nsINode* node = nullptr;
bool fromUser = true; // XXX: Determine if this was from user input.
RefPtr<xpcAccScrollingEvent> event =
new xpcAccScrollingEvent(aType, xpcAcc, doc, node, fromUser, aScrollX,
aScrollY, aMaxScrollX, aMaxScrollY);
nsCoreUtils::DispatchAccEvent(std::move(event));
return IPC_OK();
}
mozilla::ipc::IPCResult
DocAccessibleParent::RecvRoleChangedEvent(const a11y::role& aRole)
{

Просмотреть файл

@ -117,6 +117,13 @@ public:
const int16_t& aBoundaryType,
const bool& aFromUser) override;
virtual mozilla::ipc::IPCResult RecvScrollingEvent(const uint64_t& aID,
const uint64_t& aType,
const uint32_t& aScrollX,
const uint32_t& aScrollY,
const uint32_t& aMaxScrollX,
const uint32_t& aMaxScrollY) override;
mozilla::ipc::IPCResult RecvRoleChangedEvent(const a11y::role& aRole) final;
virtual mozilla::ipc::IPCResult RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID) override;

Просмотреть файл

@ -72,6 +72,9 @@ parent:
int32_t aStartOffset, int32_t aEndOffset,
int16_t aReason, int16_t aBoundaryType,
bool aFromUservcEvent);
async ScrollingEvent(uint64_t aID, uint64_t aType,
uint32_t aScrollX, uint32_t aScrollY,
uint32_t aMaxScrollX, uint32_t aMaxScrollY);
/*
* Tell the parent document to bind the existing document as a new child

Просмотреть файл

@ -70,6 +70,9 @@ parent:
int32_t aStartOffset, int32_t aEndOffset,
int16_t aReason, int16_t aBoundaryType,
bool aFromUservcEvent);
async ScrollingEvent(uint64_t aID, uint64_t aType,
uint32_t aScrollX, uint32_t aScrollY,
uint32_t aMaxScrollX, uint32_t aMaxScrollY);
/*
* Tell the parent document to bind the existing document as a new child

Просмотреть файл

@ -67,4 +67,9 @@ a11y::ProxyVirtualCursorChangeEvent(ProxyAccessible*, ProxyAccessible*,
int32_t, int32_t, int16_t, int16_t, bool)
{
}
void
a11y::ProxyScrollingEvent(ProxyAccessible*, uint32_t, uint32_t, uint32_t, uint32_t)
{
}
#endif

Просмотреть файл

@ -9,7 +9,8 @@
/* import-globals-from shared-head.js */
/* import-globals-from ../mochitest/common.js */
/* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
/* exported EVENT_REORDER, EVENT_SCROLLING, EVENT_SCROLLING_END, EVENT_SHOW,
EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED,
EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE,
EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS,
@ -20,6 +21,8 @@
const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
const EVENT_SCROLLING = nsIAccessibleEvent.EVENT_SCROLLING;
const EVENT_SCROLLING_END = nsIAccessibleEvent.EVENT_SCROLLING_END;
const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;

Просмотреть файл

@ -7,6 +7,7 @@ support-files =
[browser_test_docload.js]
skip-if = e10s
[browser_test_scrolling.js]
[browser_test_textcaret.js]
[browser_test_focus_browserui.js]
[browser_test_focus_dialog.js]

Просмотреть файл

@ -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/. */
"use strict";
addAccessibleTask(`
<div style="height: 100vh" id="one">one</div>
<div style="height: 100vh" id="two">two</div>
<div style="height: 100vh; width: 200vw" id="three">three</div>`,
async function(browser, accDoc) {
let onScrolling = waitForEvents([
[EVENT_SCROLLING, accDoc], [EVENT_SCROLLING_END, accDoc]]);
await ContentTask.spawn(browser, null, () => {
content.location.hash = "#two";
});
let [scrollEvent1, scrollEndEvent1] = await onScrolling;
scrollEvent1.QueryInterface(nsIAccessibleScrollingEvent);
ok(scrollEvent1.maxScrollY >= scrollEvent1.scrollY, "scrollY is within max");
scrollEndEvent1.QueryInterface(nsIAccessibleScrollingEvent);
ok(scrollEndEvent1.maxScrollY >= scrollEndEvent1.scrollY,
"scrollY is within max");
onScrolling = waitForEvents([
[EVENT_SCROLLING, accDoc], [EVENT_SCROLLING_END, accDoc]]);
await ContentTask.spawn(browser, null, () => {
content.location.hash = "#three";
});
let [scrollEvent2, scrollEndEvent2] = await onScrolling;
scrollEvent2.QueryInterface(nsIAccessibleScrollingEvent);
ok(scrollEvent2.scrollY > scrollEvent1.scrollY,
`${scrollEvent2.scrollY} > ${scrollEvent1.scrollY}`);
scrollEndEvent2.QueryInterface(nsIAccessibleScrollingEvent);
ok(scrollEndEvent2.maxScrollY >= scrollEndEvent2.scrollY,
"scrollY is within max");
onScrolling = waitForEvents([
[EVENT_SCROLLING, accDoc], [EVENT_SCROLLING_END, accDoc]]);
await ContentTask.spawn(browser, null, () => {
content.scrollTo(10, 0);
});
let [scrollEvent3, scrollEndEvent3] = await onScrolling;
scrollEvent3.QueryInterface(nsIAccessibleScrollingEvent);
ok(scrollEvent3.maxScrollX >= scrollEvent3.scrollX, "scrollX is within max");
scrollEndEvent3.QueryInterface(nsIAccessibleScrollingEvent);
ok(scrollEndEvent3.maxScrollX >= scrollEndEvent3.scrollX,
"scrollY is within max");
ok(scrollEvent3.scrollX > scrollEvent2.scrollX,
`${scrollEvent3.scrollX} > ${scrollEvent2.scrollX}`);
});

Просмотреть файл

@ -8,6 +8,8 @@ const nsIAccessibleStateChangeEvent =
Ci.nsIAccessibleStateChangeEvent;
const nsIAccessibleCaretMoveEvent =
Ci.nsIAccessibleCaretMoveEvent;
const nsIAccessibleScrollingEvent =
Ci.nsIAccessibleScrollingEvent;
const nsIAccessibleTextChangeEvent =
Ci.nsIAccessibleTextChangeEvent;
const nsIAccessibleVirtualCursorChangeEvent =

Просмотреть файл

@ -98,6 +98,6 @@ static const uint32_t gWinEventMap[] = {
IA2_EVENT_HYPERTEXT_NLINKS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_NLINKS_CHANGED
IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_VIRTUALCURSOR_CHANGED
EVENT_OBJECT_VALUECHANGE // nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE
EVENT_OBJECT_VALUECHANGE, // nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_SCROLLING
};

Просмотреть файл

@ -14,5 +14,6 @@ simple_events = [
'CaretMoveEvent',
'ObjectAttributeChangedEvent',
'TableChangeEvent',
'VirtualCursorChangeEvent'
'VirtualCursorChangeEvent',
'ScrollingEvent'
]