зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1611060 - Dispatch a11y scroll events for all DOM scrolls. r=Jamie
Currently, only documents dispatch scroll events when in reality any element can have scrollable overflows. Differential Revision: https://phabricator.services.mozilla.com/D62665 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
171ffe820f
Коммит
a98323bb51
|
@ -638,10 +638,6 @@ void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
|
|||
"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();
|
||||
|
|
|
@ -77,27 +77,6 @@ inline void DocAccessible::UpdateText(nsIContent* aTextNode) {
|
|||
mNotificationController->ScheduleTextUpdate(aTextNode);
|
||||
}
|
||||
|
||||
inline void DocAccessible::AddScrollListener() {
|
||||
// Delay scroll initializing until the document has a root frame.
|
||||
if (!mPresShell->GetRootFrame()) return;
|
||||
|
||||
mDocFlags |= eScrollInitialized;
|
||||
nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
|
||||
if (sf) {
|
||||
sf->AddScrollPositionListener(this);
|
||||
|
||||
#ifdef A11Y_LOG
|
||||
if (logging::IsEnabled(logging::eDocCreate))
|
||||
logging::Text("add scroll listener");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
inline void DocAccessible::RemoveScrollListener() {
|
||||
nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
|
||||
if (sf) sf->RemoveScrollPositionListener(this);
|
||||
}
|
||||
|
||||
inline void DocAccessible::NotifyOfLoad(uint32_t aLoadEventType) {
|
||||
mLoadState |= eDOMLoaded;
|
||||
mLoadEventType = aLoadEventType;
|
||||
|
|
|
@ -81,7 +81,6 @@ DocAccessible::DocAccessible(dom::Document* aDocument,
|
|||
mAccessibleCache(kDefaultCacheLength),
|
||||
mNodeToAccessibleMap(kDefaultCacheLength),
|
||||
mDocumentNode(aDocument),
|
||||
mScrollPositionChangedTicks(0),
|
||||
mLoadState(eTreeConstructionPending),
|
||||
mDocFlags(0),
|
||||
mLoadEventType(0),
|
||||
|
@ -510,9 +509,6 @@ nsresult DocAccessible::AddEventListeners() {
|
|||
// DocAccessible protected member
|
||||
nsresult DocAccessible::RemoveEventListeners() {
|
||||
// Remove listeners associated with content documents
|
||||
// Remove scroll position listener
|
||||
RemoveScrollListener();
|
||||
|
||||
NS_ASSERTION(mDocumentNode, "No document during removal of listeners.");
|
||||
|
||||
if (mDocumentNode) {
|
||||
|
@ -545,7 +541,14 @@ void DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure) {
|
|||
DocAccessible* docAcc = reinterpret_cast<DocAccessible*>(aClosure);
|
||||
|
||||
if (docAcc) {
|
||||
docAcc->DispatchScrollingEvent(nsIAccessibleEvent::EVENT_SCROLLING_END);
|
||||
// Dispatch a scroll-end for all entries in table. They have not
|
||||
// been scrolled in at least `kScrollEventInterval`.
|
||||
for (auto iter = docAcc->mLastScrollingDispatch.Iter(); !iter.Done();
|
||||
iter.Next()) {
|
||||
docAcc->DispatchScrollingEvent(iter.Key(),
|
||||
nsIAccessibleEvent::EVENT_SCROLLING_END);
|
||||
iter.Remove();
|
||||
}
|
||||
|
||||
if (docAcc->mScrollWatchTimer) {
|
||||
docAcc->mScrollWatchTimer = nullptr;
|
||||
|
@ -554,17 +557,16 @@ void DocAccessible::ScrollTimerCallback(nsITimer* aTimer, void* aClosure) {
|
|||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIScrollPositionListener
|
||||
|
||||
void DocAccessible::ScrollPositionDidChange(nscoord aX, nscoord aY) {
|
||||
void DocAccessible::HandleScroll(nsINode* aTarget) {
|
||||
const uint32_t kScrollEventInterval = 100;
|
||||
TimeStamp timestamp = TimeStamp::Now();
|
||||
if (mLastScrollingDispatch.IsNull() ||
|
||||
(timestamp - mLastScrollingDispatch).ToMilliseconds() >=
|
||||
kScrollEventInterval) {
|
||||
DispatchScrollingEvent(nsIAccessibleEvent::EVENT_SCROLLING);
|
||||
mLastScrollingDispatch = timestamp;
|
||||
TimeStamp now = TimeStamp::Now();
|
||||
TimeStamp lastDispatch;
|
||||
// If we haven't dispatched a scrolling event for a target in at least
|
||||
// kScrollEventInterval milliseconds, dispatch one now.
|
||||
if (!mLastScrollingDispatch.Get(aTarget, &lastDispatch) ||
|
||||
(now - lastDispatch).ToMilliseconds() >= kScrollEventInterval) {
|
||||
DispatchScrollingEvent(aTarget, nsIAccessibleEvent::EVENT_SCROLLING);
|
||||
mLastScrollingDispatch.Put(aTarget, now);
|
||||
}
|
||||
|
||||
// If timer callback is still pending, push it 100ms into the future.
|
||||
|
@ -2522,27 +2524,38 @@ void DocAccessible::SetIPCDoc(DocAccessibleChild* aIPCDoc) {
|
|||
mIPCDoc = aIPCDoc;
|
||||
}
|
||||
|
||||
void DocAccessible::DispatchScrollingEvent(uint32_t aEventType) {
|
||||
nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
|
||||
if (!sf) {
|
||||
void DocAccessible::DispatchScrollingEvent(nsINode* aTarget,
|
||||
uint32_t aEventType) {
|
||||
Accessible* acc = GetAccessible(aTarget);
|
||||
if (!acc) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t appUnitsPerDevPixel =
|
||||
mPresShell->GetPresContext()->AppUnitsPerDevPixel();
|
||||
LayoutDevicePoint scrollPoint =
|
||||
LayoutDevicePoint::FromAppUnits(sf->GetScrollPosition(),
|
||||
appUnitsPerDevPixel) *
|
||||
mPresShell->GetResolution();
|
||||
LayoutDevicePoint scrollPoint;
|
||||
LayoutDeviceRect scrollRange;
|
||||
nsIScrollableFrame* sf = acc == this
|
||||
? mPresShell->GetRootScrollFrameAsScrollable()
|
||||
: acc->GetFrame()->GetScrollTargetFrame();
|
||||
|
||||
LayoutDeviceRect scrollRange =
|
||||
LayoutDeviceRect::FromAppUnits(sf->GetScrollRange(), appUnitsPerDevPixel);
|
||||
scrollRange.ScaleRoundOut(mPresShell->GetResolution());
|
||||
// If there is no scrollable frame, it's likely a scroll in a popup, like
|
||||
// <select>. Just send an event with no scroll info. The scroll info
|
||||
// is currently only used on Android, and popups are rendered natively
|
||||
// there.
|
||||
if (sf) {
|
||||
int32_t appUnitsPerDevPixel =
|
||||
mPresShell->GetPresContext()->AppUnitsPerDevPixel();
|
||||
scrollPoint = LayoutDevicePoint::FromAppUnits(sf->GetScrollPosition(),
|
||||
appUnitsPerDevPixel) *
|
||||
mPresShell->GetResolution();
|
||||
|
||||
scrollRange = LayoutDeviceRect::FromAppUnits(sf->GetScrollRange(),
|
||||
appUnitsPerDevPixel);
|
||||
scrollRange.ScaleRoundOut(mPresShell->GetResolution());
|
||||
}
|
||||
|
||||
RefPtr<AccEvent> event =
|
||||
new AccScrollingEvent(aEventType, this, scrollPoint.x, scrollPoint.y,
|
||||
new AccScrollingEvent(aEventType, acc, scrollPoint.x, scrollPoint.y,
|
||||
scrollRange.width, scrollRange.height);
|
||||
|
||||
nsEventShell::FireEvent(event);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
#include "mozilla/dom/Document.h"
|
||||
#include "nsIDocumentObserver.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsIScrollPositionListener.h"
|
||||
#include "nsITimer.h"
|
||||
|
||||
class nsAccessiblePivot;
|
||||
|
@ -45,7 +44,6 @@ class TNotification;
|
|||
class DocAccessible : public HyperTextAccessibleWrap,
|
||||
public nsIDocumentObserver,
|
||||
public nsIObserver,
|
||||
public nsIScrollPositionListener,
|
||||
public nsSupportsWeakReference,
|
||||
public nsIAccessiblePivotObserver {
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
@ -60,10 +58,6 @@ class DocAccessible : public HyperTextAccessibleWrap,
|
|||
public:
|
||||
DocAccessible(Document* aDocument, PresShell* aPresShell);
|
||||
|
||||
// nsIScrollPositionListener
|
||||
virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) override {}
|
||||
virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) override;
|
||||
|
||||
// nsIDocumentObserver
|
||||
NS_DECL_NSIDOCUMENTOBSERVER
|
||||
|
||||
|
@ -384,6 +378,13 @@ class DocAccessible : public HyperTextAccessibleWrap,
|
|||
*/
|
||||
DocAccessibleChild* IPCDoc() const { return mIPCDoc; }
|
||||
|
||||
/**
|
||||
* Notify the document that a DOM node has been scrolled. document will
|
||||
* dispatch throttled accessibility events for scrolling, and a scroll-end
|
||||
* event.
|
||||
*/
|
||||
void HandleScroll(nsINode* aTarget);
|
||||
|
||||
protected:
|
||||
virtual ~DocAccessible();
|
||||
|
||||
|
@ -418,12 +419,6 @@ class DocAccessible : public HyperTextAccessibleWrap,
|
|||
*/
|
||||
void ProcessLoad();
|
||||
|
||||
/**
|
||||
* Add/remove scroll listeners, @see nsIScrollPositionListener interface.
|
||||
*/
|
||||
void AddScrollListener();
|
||||
void RemoveScrollListener();
|
||||
|
||||
/**
|
||||
* Append the given document accessible to this document's child document
|
||||
* accessibles.
|
||||
|
@ -577,7 +572,7 @@ class DocAccessible : public HyperTextAccessibleWrap,
|
|||
*/
|
||||
static void ScrollTimerCallback(nsITimer* aTimer, void* aClosure);
|
||||
|
||||
void DispatchScrollingEvent(uint32_t aEventType);
|
||||
void DispatchScrollingEvent(nsINode* aTarget, uint32_t aEventType);
|
||||
|
||||
/**
|
||||
* Check if an id attribute change affects aria-activedescendant and handle
|
||||
|
@ -607,11 +602,8 @@ class DocAccessible : public HyperTextAccessibleWrap,
|
|||
* State and property flags, kept by mDocFlags.
|
||||
*/
|
||||
enum {
|
||||
// Whether scroll listeners were added.
|
||||
eScrollInitialized = 1 << 0,
|
||||
|
||||
// Whether the document is a tab document.
|
||||
eTabDocument = 1 << 1
|
||||
eTabDocument = 1 << 0
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -623,8 +615,7 @@ class DocAccessible : public HyperTextAccessibleWrap,
|
|||
|
||||
Document* mDocumentNode;
|
||||
nsCOMPtr<nsITimer> mScrollWatchTimer;
|
||||
uint16_t mScrollPositionChangedTicks; // Used for tracking scroll events
|
||||
TimeStamp mLastScrollingDispatch;
|
||||
nsDataHashtable<nsPtrHashKey<nsINode>, TimeStamp> mLastScrollingDispatch;
|
||||
|
||||
/**
|
||||
* Bit mask of document load states (@see LoadState).
|
||||
|
|
|
@ -144,7 +144,7 @@ const char* const kEventTypes[] = {
|
|||
// HTMLInputElement.cpp & radio.js)
|
||||
"RadioStateChange", "popupshown", "popuphiding", "DOMMenuInactive",
|
||||
"DOMMenuItemActive", "DOMMenuItemInactive", "DOMMenuBarActive",
|
||||
"DOMMenuBarInactive"};
|
||||
"DOMMenuBarInactive", "scroll"};
|
||||
|
||||
nsresult RootAccessible::AddEventListeners() {
|
||||
// EventTarget interface allows to register event listeners to
|
||||
|
@ -222,13 +222,23 @@ RootAccessible::HandleEvent(Event* aDOMEvent) {
|
|||
GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc());
|
||||
|
||||
if (document) {
|
||||
// Root accessible exists longer than any of its descendant documents so
|
||||
// that we are guaranteed notification is processed before root accessible
|
||||
// is destroyed.
|
||||
// For shadow DOM, GetOriginalTarget on the Event returns null if we
|
||||
// process the event async, so we must pass the target node as well.
|
||||
document->HandleNotification<RootAccessible, Event, nsINode>(
|
||||
this, &RootAccessible::ProcessDOMEvent, aDOMEvent, origTargetNode);
|
||||
nsAutoString eventType;
|
||||
aDOMEvent->GetType(eventType);
|
||||
if (eventType.EqualsLiteral("scroll")) {
|
||||
// We don't put this in the notification queue for 2 reasons:
|
||||
// 1. We will flood the queue with repetitive events.
|
||||
// 2. Since this doesn't necessarily touch layout, we are not
|
||||
// guaranteed to have a WillRefresh tick any time soon.
|
||||
document->HandleScroll(origTargetNode);
|
||||
} else {
|
||||
// Root accessible exists longer than any of its descendant documents so
|
||||
// that we are guaranteed notification is processed before root accessible
|
||||
// is destroyed.
|
||||
// For shadow DOM, GetOriginalTarget on the Event returns null if we
|
||||
// process the event async, so we must pass the target node as well.
|
||||
document->HandleNotification<RootAccessible, Event, nsINode>(
|
||||
this, &RootAccessible::ProcessDOMEvent, aDOMEvent, origTargetNode);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
|
|
@ -8,7 +8,9 @@ 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>`,
|
||||
<div style="height: 100vh; width: 200vw; overflow: auto;" id="three">
|
||||
<div style="height: 300%;">three</div>
|
||||
</div>`,
|
||||
async function(browser, accDoc) {
|
||||
let onScrolling = waitForEvents([
|
||||
[EVENT_SCROLLING, accDoc],
|
||||
|
@ -70,5 +72,25 @@ addAccessibleTask(
|
|||
scrollEvent3.scrollX > scrollEvent2.scrollX,
|
||||
`${scrollEvent3.scrollX} > ${scrollEvent2.scrollX}`
|
||||
);
|
||||
|
||||
// non-doc scrolling
|
||||
onScrolling = waitForEvents([
|
||||
[EVENT_SCROLLING, "three"],
|
||||
[EVENT_SCROLLING_END, "three"],
|
||||
]);
|
||||
await SpecialPowers.spawn(browser, [], () => {
|
||||
content.document.querySelector("#three").scrollTo(0, 10);
|
||||
});
|
||||
let [scrollEvent4, scrollEndEvent4] = await onScrolling;
|
||||
scrollEvent4.QueryInterface(nsIAccessibleScrollingEvent);
|
||||
ok(
|
||||
scrollEvent4.maxScrollY >= scrollEvent4.scrollY,
|
||||
"scrollY is within max"
|
||||
);
|
||||
scrollEndEvent4.QueryInterface(nsIAccessibleScrollingEvent);
|
||||
ok(
|
||||
scrollEndEvent4.maxScrollY >= scrollEndEvent4.scrollY,
|
||||
"scrollY is within max"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
Загрузка…
Ссылка в новой задаче