Bug 1787274: Use mCachedFields viewport cache for determining offscreen state r=Jamie,geckoview-reviewers,owlish
Differential Revision: https://phabricator.services.mozilla.com/D155903
This commit is contained in:
@@ -889,6 +889,18 @@ void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
|
|||||||
|
|
||||||
CoalesceMutationEvents();
|
CoalesceMutationEvents();
|
||||||
ProcessMutationEvents();
|
ProcessMutationEvents();
|
||||||
|
|
||||||
|
// When firing mutation events, mObservingState is set to
|
||||||
|
// eRefreshProcessing. Any calls to ScheduleProcessing() that
|
||||||
|
// occur before mObservingState is reset will be dropped because we only
|
||||||
|
// schedule a tick if mObservingState == eNotObservingRefresh.
|
||||||
|
// This sometimes results in our viewport cache being out-of-date after
|
||||||
|
// processing mutation events. Call ProcessQueuedCacheUpdates again to
|
||||||
|
// ensure it is updated.
|
||||||
|
if (IPCAccessibilityActive() && mDocument) {
|
||||||
|
mDocument->ProcessQueuedCacheUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
mEventGeneration = 0;
|
mEventGeneration = 0;
|
||||||
|
|
||||||
// Now that we are done with them get rid of the events we fired.
|
// Now that we are done with them get rid of the events we fired.
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ class DocAccessible : public HyperTextAccessibleWrap,
|
|||||||
|
|
||||||
bool IsHidden() const;
|
bool IsHidden() const;
|
||||||
|
|
||||||
bool IsViewportCacheDirty() { return mViewportCacheDirty; }
|
|
||||||
void SetViewportCacheDirty(bool aDirty) { mViewportCacheDirty = aDirty; }
|
void SetViewportCacheDirty(bool aDirty) { mViewportCacheDirty = aDirty; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3192,6 +3192,12 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
|
|||||||
|
|
||||||
nsTHashSet<LocalAccessible*> inViewAccs;
|
nsTHashSet<LocalAccessible*> inViewAccs;
|
||||||
nsTArray<uint64_t> viewportCache;
|
nsTArray<uint64_t> viewportCache;
|
||||||
|
// Layout considers table rows fully occluded by their containing cells.
|
||||||
|
// This means they don't have their own display list items, and they won't
|
||||||
|
// show up in the list returned from GetFramesForArea. To prevent table
|
||||||
|
// rows from appearing offscreen, we manually add any rows for which we
|
||||||
|
// have on-screen cells.
|
||||||
|
LocalAccessible* prevParentRow = nullptr;
|
||||||
for (nsIFrame* frame : frames) {
|
for (nsIFrame* frame : frames) {
|
||||||
nsIContent* content = frame->GetContent();
|
nsIContent* content = frame->GetContent();
|
||||||
if (!content) {
|
if (!content) {
|
||||||
@@ -3210,8 +3216,28 @@ already_AddRefed<AccAttributes> LocalAccessible::BundleFieldsForCache(
|
|||||||
if (acc->IsTextLeaf() && nsAccUtils::MustPrune(acc->LocalParent())) {
|
if (acc->IsTextLeaf() && nsAccUtils::MustPrune(acc->LocalParent())) {
|
||||||
acc = acc->LocalParent();
|
acc = acc->LocalParent();
|
||||||
}
|
}
|
||||||
|
if (acc->IsTableCell()) {
|
||||||
if (acc->IsImageMap()) {
|
LocalAccessible* parent = acc->LocalParent();
|
||||||
|
if (parent && parent->IsTableRow() && parent != prevParentRow) {
|
||||||
|
// If we've entered a new row since the last cell we saw, add the
|
||||||
|
// previous parent row to our viewport cache here to maintain
|
||||||
|
// hittesting order. Keep track of the current parent row.
|
||||||
|
if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
|
||||||
|
viewportCache.AppendElement(prevParentRow->ID());
|
||||||
|
}
|
||||||
|
prevParentRow = parent;
|
||||||
|
}
|
||||||
|
} else if (acc->IsTable()) {
|
||||||
|
// If we've encountered a table, we know we've already
|
||||||
|
// handled all of this table's content (because we're traversing
|
||||||
|
// in hittesting order). Add our table's final row to the viewport
|
||||||
|
// cache before adding the table itself. Reset our marker for the next
|
||||||
|
// table.
|
||||||
|
if (prevParentRow && inViewAccs.EnsureInserted(prevParentRow)) {
|
||||||
|
viewportCache.AppendElement(prevParentRow->ID());
|
||||||
|
}
|
||||||
|
prevParentRow = nullptr;
|
||||||
|
} else if (acc->IsImageMap()) {
|
||||||
// Layout doesn't walk image maps, so we do that
|
// Layout doesn't walk image maps, so we do that
|
||||||
// manually here. We do this before adding the map itself
|
// manually here. We do this before adding the map itself
|
||||||
// so the children come earlier in the hittesting order.
|
// so the children come earlier in the hittesting order.
|
||||||
|
|||||||
@@ -338,6 +338,10 @@ class DocAccessibleParent : public RemoteAccessible,
|
|||||||
nsTHashMap<uint64_t, nsTHashMap<uint64_t, nsTArray<uint64_t>>>
|
nsTHashMap<uint64_t, nsTHashMap<uint64_t, nsTArray<uint64_t>>>
|
||||||
mReverseRelations;
|
mReverseRelations;
|
||||||
|
|
||||||
|
// Computed from the viewport cache, the accs referenced by these ids
|
||||||
|
// are currently on screen (making any acc not in this list offscreen).
|
||||||
|
nsTHashSet<uint64_t> mOnScreenAccessibles;
|
||||||
|
|
||||||
static DocAccessibleParent* GetFrom(dom::BrowsingContext* aBrowsingContext);
|
static DocAccessibleParent* GetFrom(dom::BrowsingContext* aBrowsingContext);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1003,17 +1003,61 @@ uint64_t RemoteAccessibleBase<Derived>::State() {
|
|||||||
state &= ~states::OPAQUE1;
|
state &= ~states::OPAQUE1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto* browser = static_cast<dom::BrowserParent*>(Document()->Manager());
|
||||||
|
if (browser == dom::BrowserParent::GetFocused()) {
|
||||||
|
if (this == Document()->GetFocusedAcc()) {
|
||||||
|
state |= states::FOCUSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto* cbc = mDoc->GetBrowsingContext();
|
auto* cbc = mDoc->GetBrowsingContext();
|
||||||
if (cbc && !cbc->IsActive()) {
|
if (cbc && !cbc->IsActive()) {
|
||||||
|
// If our browsing context is _not_ active, we're in a background tab
|
||||||
|
// and inherently offscreen.
|
||||||
state |= states::OFFSCREEN;
|
state |= states::OFFSCREEN;
|
||||||
|
} else {
|
||||||
|
// If we're in an active browsing context, there are a few scenarios we
|
||||||
|
// need to address:
|
||||||
|
// - We are an iframe document in the visual viewport
|
||||||
|
// - We are an iframe document out of the visual viewport
|
||||||
|
// - We are non-iframe content in the visual viewport
|
||||||
|
// - We are non-iframe content out of the visual viewport
|
||||||
|
// We assume top level tab docs are on screen if their BC is active, so
|
||||||
|
// we don't need additional handling for them here.
|
||||||
|
if (!mDoc->IsTopLevel()) {
|
||||||
|
// Here we handle iframes and iframe content.
|
||||||
|
// We use an iframe's outer doc's position in the embedding document's
|
||||||
|
// viewport to determine if the iframe has been scrolled offscreen.
|
||||||
|
Accessible* docParent = mDoc->Parent();
|
||||||
|
// In rare cases, we might not have an outer doc yet. Return if that's
|
||||||
|
// the case.
|
||||||
|
if (NS_WARN_IF(!docParent || !docParent->IsRemote())) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteAccessible* outerDoc = docParent->AsRemote();
|
||||||
|
DocAccessibleParent* embeddingDocument = outerDoc->Document();
|
||||||
|
if (embeddingDocument &&
|
||||||
|
!embeddingDocument->mOnScreenAccessibles.Contains(outerDoc->ID())) {
|
||||||
|
// Our embedding document's viewport cache doesn't contain the ID of
|
||||||
|
// our outer doc, so this iframe (and any of its content) is
|
||||||
|
// offscreen.
|
||||||
|
state |= states::OFFSCREEN;
|
||||||
|
} else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
|
||||||
|
// Our embedding document's viewport cache contains the ID of our
|
||||||
|
// outer doc, but the iframe's viewport cache doesn't contain our ID.
|
||||||
|
// We are offscreen.
|
||||||
|
state |= states::OFFSCREEN;
|
||||||
|
}
|
||||||
|
} else if (this != mDoc && !mDoc->mOnScreenAccessibles.Contains(ID())) {
|
||||||
|
// We are top level tab content (but not a top level tab doc).
|
||||||
|
// If our tab doc's viewport cache doesn't contain our ID, we're
|
||||||
|
// offscreen.
|
||||||
|
state |= states::OFFSCREEN;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto* browser = static_cast<dom::BrowserParent*>(Document()->Manager());
|
|
||||||
if (browser == dom::BrowserParent::GetFocused()) {
|
|
||||||
if (this == Document()->GetFocusedAcc()) {
|
|
||||||
state |= states::FOCUSED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -258,6 +258,19 @@ class RemoteAccessibleBase : public Accessible, public HyperTextAccessibleBase {
|
|||||||
|
|
||||||
void ApplyCache(CacheUpdateType aUpdateType, AccAttributes* aFields) {
|
void ApplyCache(CacheUpdateType aUpdateType, AccAttributes* aFields) {
|
||||||
const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields);
|
const nsTArray<bool> relUpdatesNeeded = PreProcessRelations(aFields);
|
||||||
|
if (auto maybeViewportCache =
|
||||||
|
aFields->GetAttribute<nsTArray<uint64_t>>(nsGkAtoms::viewport)) {
|
||||||
|
// Updating the viewport cache means the offscreen state of this
|
||||||
|
// document's accessibles has changed. Update the HashSet we use for
|
||||||
|
// checking offscreen state here.
|
||||||
|
MOZ_ASSERT(IsDoc(),
|
||||||
|
"Fetched the viewport cache from a non-doc accessible?");
|
||||||
|
AsDoc()->mOnScreenAccessibles.Clear();
|
||||||
|
for (auto id : *maybeViewportCache) {
|
||||||
|
AsDoc()->mOnScreenAccessibles.Insert(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (aUpdateType == CacheUpdateType::Initial) {
|
if (aUpdateType == CacheUpdateType::Initial) {
|
||||||
mCachedFields = aFields;
|
mCachedFields = aFields;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ skip-if = verify
|
|||||||
https_first_disabled = true
|
https_first_disabled = true
|
||||||
skip-if =
|
skip-if =
|
||||||
os == 'win' && bits == 64 && !debug # bug 1652192
|
os == 'win' && bits == 64 && !debug # bug 1652192
|
||||||
[browser_offscreen_element_in_out_of_process_iframe.js]
|
[browser_test_visibility_2.js]
|
||||||
|
https_first_disabled = true
|
||||||
skip-if =
|
skip-if =
|
||||||
os == 'win' # bug 1580706
|
os == 'win' && bits == 64 && !debug # bug 1652192
|
||||||
apple_catalina # high frequency intermittent
|
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
/* 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";
|
|
||||||
|
|
||||||
const parentURL =
|
|
||||||
"data:text/html;charset=utf-8," +
|
|
||||||
'<div id="scroller" style="width: 300px; height: 300px; overflow-y: scroll; overflow-x: hidden;">' +
|
|
||||||
' <div style="width: 100%; height: 1000px;"></div>' +
|
|
||||||
' <iframe frameborder="0"/>' +
|
|
||||||
"</div>";
|
|
||||||
const iframeURL =
|
|
||||||
"data:text/html;charset=utf-8," +
|
|
||||||
"<style>" +
|
|
||||||
" html,body {" +
|
|
||||||
" /* Convenient for calculation of element positions */" +
|
|
||||||
" margin: 0;" +
|
|
||||||
" padding: 0;" +
|
|
||||||
" }" +
|
|
||||||
"</style>" +
|
|
||||||
'<div id="target" style="width: 100px; height: 100px;">target</div>';
|
|
||||||
|
|
||||||
add_task(async function() {
|
|
||||||
const win = await BrowserTestUtils.openNewBrowserWindow({
|
|
||||||
fission: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const browser = win.gBrowser.selectedTab.linkedBrowser;
|
|
||||||
|
|
||||||
BrowserTestUtils.loadURI(browser, parentURL);
|
|
||||||
await BrowserTestUtils.browserLoaded(browser, false, parentURL);
|
|
||||||
|
|
||||||
async function setup(url) {
|
|
||||||
const iframe = content.document.querySelector("iframe");
|
|
||||||
|
|
||||||
iframe.contentWindow.location = url;
|
|
||||||
await new Promise(resolve => {
|
|
||||||
iframe.addEventListener("load", resolve, { once: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
return iframe.browsingContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function scrollTo(x, y) {
|
|
||||||
await SpecialPowers.spawn(browser, [x, y], async (scrollX, scrollY) => {
|
|
||||||
const scroller = content.document.getElementById("scroller");
|
|
||||||
scroller.scrollTo(scrollX, scrollY);
|
|
||||||
await new Promise(resolve => {
|
|
||||||
scroller.addEventListener("scroll", resolve, { once: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
await waitForIFrameUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup an out-of-process iframe which is initially scrolled out.
|
|
||||||
const iframe = await SpecialPowers.spawn(browser, [iframeURL], setup);
|
|
||||||
|
|
||||||
await waitForIFrameA11yReady(iframe);
|
|
||||||
await spawnTestStates(
|
|
||||||
iframe,
|
|
||||||
"target",
|
|
||||||
nsIAccessibleStates.STATE_OFFSCREEN,
|
|
||||||
nsIAccessibleStates.STATE_INVISIBLE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scroll the iframe into view and the target element is also visible but
|
|
||||||
// the visible area height is 11px.
|
|
||||||
await scrollTo(0, 711);
|
|
||||||
await spawnTestStates(
|
|
||||||
iframe,
|
|
||||||
"target",
|
|
||||||
nsIAccessibleStates.STATE_OFFSCREEN,
|
|
||||||
nsIAccessibleStates.STATE_INVISIBLE
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scroll to a position where the visible height is 13px.
|
|
||||||
await scrollTo(0, 713);
|
|
||||||
await spawnTestStates(
|
|
||||||
iframe,
|
|
||||||
"target",
|
|
||||||
0,
|
|
||||||
nsIAccessibleStates.STATE_OFFSCREEN
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scroll the iframe out again.
|
|
||||||
await scrollTo(0, 0);
|
|
||||||
await spawnTestStates(
|
|
||||||
iframe,
|
|
||||||
"target",
|
|
||||||
nsIAccessibleStates.STATE_OFFSCREEN,
|
|
||||||
nsIAccessibleStates.STATE_INVISIBLE
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await BrowserTestUtils.closeWindow(win);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -4,13 +4,6 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* import-globals-from ../../mochitest/role.js */
|
|
||||||
/* import-globals-from ../../mochitest/states.js */
|
|
||||||
loadScripts(
|
|
||||||
{ name: "role.js", dir: MOCHITESTS_DIR },
|
|
||||||
{ name: "states.js", dir: MOCHITESTS_DIR }
|
|
||||||
);
|
|
||||||
|
|
||||||
async function runTests(browser, accDoc) {
|
async function runTests(browser, accDoc) {
|
||||||
let getAcc = id => findAccessibleChildByID(accDoc, id);
|
let getAcc = id => findAccessibleChildByID(accDoc, id);
|
||||||
|
|
||||||
|
|||||||
@@ -4,42 +4,110 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* import-globals-from ../../mochitest/role.js */
|
|
||||||
/* import-globals-from ../../mochitest/states.js */
|
|
||||||
loadScripts(
|
|
||||||
{ name: "role.js", dir: MOCHITESTS_DIR },
|
|
||||||
{ name: "states.js", dir: MOCHITESTS_DIR }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Temporary debug logging for bug 1652192.
|
|
||||||
EventsLogger.enabled = true;
|
|
||||||
|
|
||||||
async function runTest(browser, accDoc) {
|
async function runTest(browser, accDoc) {
|
||||||
let getAcc = id => findAccessibleChildByID(accDoc, id);
|
let getAcc = id => findAccessibleChildByID(accDoc, id);
|
||||||
|
|
||||||
testStates(getAcc("div"), 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
|
await untilCacheOk(
|
||||||
|
() => testVisibility(getAcc("div"), false, false),
|
||||||
|
"Div should be on screen"
|
||||||
|
);
|
||||||
|
|
||||||
let input = getAcc("input_scrolledoff");
|
let input = getAcc("input_scrolledoff");
|
||||||
testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
|
await untilCacheOk(
|
||||||
|
() => testVisibility(input, true, false),
|
||||||
|
"Input should be offscreen"
|
||||||
|
);
|
||||||
|
|
||||||
// scrolled off item (twice)
|
// scrolled off item (twice)
|
||||||
let lastLi = getAcc("li_last");
|
let lastLi = getAcc("li_last");
|
||||||
testStates(lastLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
|
await untilCacheOk(
|
||||||
|
() => testVisibility(lastLi, true, false),
|
||||||
|
"Last list item should be offscreen"
|
||||||
|
);
|
||||||
|
|
||||||
// scroll into view the item
|
// scroll into view the item
|
||||||
await invokeContentTask(browser, [], () => {
|
await invokeContentTask(browser, [], () => {
|
||||||
content.document.getElementById("li_last").scrollIntoView(true);
|
content.document.getElementById("li_last").scrollIntoView(true);
|
||||||
});
|
});
|
||||||
testStates(lastLi, 0, 0, STATE_OFFSCREEN | STATE_INVISIBLE);
|
await untilCacheOk(
|
||||||
|
() => testVisibility(lastLi, false, false),
|
||||||
|
"Last list item should no longer be offscreen"
|
||||||
|
);
|
||||||
|
|
||||||
// first item is scrolled off now (testcase for bug 768786)
|
// first item is scrolled off now (testcase for bug 768786)
|
||||||
let firstLi = getAcc("li_first");
|
let firstLi = getAcc("li_first");
|
||||||
testStates(firstLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
|
await untilCacheOk(
|
||||||
|
() => testVisibility(firstLi, true, false),
|
||||||
|
"First listitem should now be offscreen"
|
||||||
|
);
|
||||||
|
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(getAcc("frame"), false, false),
|
||||||
|
"iframe should initially be onscreen"
|
||||||
|
);
|
||||||
|
|
||||||
|
let loaded = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, "iframeDoc");
|
||||||
|
await invokeContentTask(browser, [], () => {
|
||||||
|
content.document.querySelector("iframe").src =
|
||||||
|
'data:text/html,<body id="iframeDoc"><p id="p">hi</p></body>';
|
||||||
|
});
|
||||||
|
|
||||||
|
const iframeDoc = (await loaded).accessible;
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(getAcc("frame"), false, false),
|
||||||
|
"iframe outer doc should now be on screen"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(iframeDoc, false, false),
|
||||||
|
"iframe inner doc should be on screen"
|
||||||
|
);
|
||||||
|
const iframeP = findAccessibleChildByID(iframeDoc, "p");
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(iframeP, false, false),
|
||||||
|
"iframe content should also be on screen"
|
||||||
|
);
|
||||||
|
|
||||||
|
// scroll into view the div
|
||||||
|
await invokeContentTask(browser, [], () => {
|
||||||
|
content.document.getElementById("div").scrollIntoView(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(getAcc("frame"), true, false),
|
||||||
|
"iframe outer doc should now be off screen"
|
||||||
|
);
|
||||||
|
// See bug 1792256
|
||||||
|
await untilCacheOk(
|
||||||
|
() => !isCacheEnabled || testVisibility(iframeDoc, true, false),
|
||||||
|
"iframe inner doc should now be off screen"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(iframeP, true, false),
|
||||||
|
"iframe content should now be off screen"
|
||||||
|
);
|
||||||
|
|
||||||
let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
|
||||||
// Accessibles in background tab should have offscreen state and no
|
// Accessibles in background tab should have offscreen state and no
|
||||||
// invisible state.
|
// invisible state.
|
||||||
testStates(getAcc("div"), STATE_OFFSCREEN, 0, STATE_INVISIBLE);
|
await untilCacheOk(
|
||||||
|
() => testVisibility(getAcc("div"), true, false),
|
||||||
|
"Accs in background tab should be offscreen but not invisible."
|
||||||
|
);
|
||||||
|
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(getAcc("frame"), true, false),
|
||||||
|
"iframe outer doc should still be off screen"
|
||||||
|
);
|
||||||
|
// See bug 1792256
|
||||||
|
await untilCacheOk(
|
||||||
|
() => !isCacheEnabled || testVisibility(iframeDoc, true, false),
|
||||||
|
"iframe inner doc should still be off screen"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(iframeP, true, false),
|
||||||
|
"iframe content should still be off screen"
|
||||||
|
);
|
||||||
|
|
||||||
BrowserTestUtils.removeTab(newTab);
|
BrowserTestUtils.removeTab(newTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +118,43 @@ addAccessibleTask(
|
|||||||
<ul style="border:2px solid red; width: 100px; height: 50px; overflow: auto;">
|
<ul style="border:2px solid red; width: 100px; height: 50px; overflow: auto;">
|
||||||
<li id="li_first">item1</li><li>item2</li><li>item3</li>
|
<li id="li_first">item1</li><li>item2</li><li>item3</li>
|
||||||
<li>item4</li><li>item5</li><li id="li_last">item6</li>
|
<li>item4</li><li>item5</li><li id="li_last">item6</li>
|
||||||
</ul>`,
|
</ul>
|
||||||
|
<iframe id="frame"></iframe>
|
||||||
|
`,
|
||||||
runTest,
|
runTest,
|
||||||
|
{ chrome: !isCacheEnabled, iframe: true, remoteIframe: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test div containers are reported as onscreen, even if some of their contents are
|
||||||
|
* offscreen.
|
||||||
|
*/
|
||||||
|
addAccessibleTask(
|
||||||
|
`
|
||||||
|
<div id="outer" style="width:200vw; background: green; overflow:scroll;"><div id="inner"><div style="display:inline-block; width:100vw; background:red;" id="on">on screen</div><div style="background:blue; display:inline;" id="off">offscreen</div></div></div>
|
||||||
|
`,
|
||||||
|
async function(browser, accDoc) {
|
||||||
|
const outer = findAccessibleChildByID(accDoc, "outer");
|
||||||
|
const inner = findAccessibleChildByID(accDoc, "inner");
|
||||||
|
const on = findAccessibleChildByID(accDoc, "on");
|
||||||
|
const off = findAccessibleChildByID(accDoc, "off");
|
||||||
|
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(outer, false, false),
|
||||||
|
"outer should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(inner, false, false),
|
||||||
|
"inner should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(on, false, false),
|
||||||
|
"on should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(off, true, false),
|
||||||
|
"off should be off screen and visible"
|
||||||
|
);
|
||||||
|
},
|
||||||
{ chrome: true, iframe: true, remoteIframe: true }
|
{ chrome: true, iframe: true, remoteIframe: true }
|
||||||
);
|
);
|
||||||
|
|||||||
131
accessible/tests/browser/states/browser_test_visibility_2.js
Normal file
131
accessible/tests/browser/states/browser_test_visibility_2.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/* 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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test tables, table rows are reported on screen, even if some cells of a given row are
|
||||||
|
* offscreen.
|
||||||
|
*/
|
||||||
|
addAccessibleTask(
|
||||||
|
`
|
||||||
|
<table id="table" style="width:150vw;" border><tr id="row"><td id="one" style="width:50vw;">one</td><td style="width:50vw;" id="two">two</td><td id="three">three</td></tr></table>
|
||||||
|
`,
|
||||||
|
async function(browser, accDoc) {
|
||||||
|
const table = findAccessibleChildByID(accDoc, "table");
|
||||||
|
const row = findAccessibleChildByID(accDoc, "row");
|
||||||
|
const one = findAccessibleChildByID(accDoc, "one");
|
||||||
|
const two = findAccessibleChildByID(accDoc, "two");
|
||||||
|
const three = findAccessibleChildByID(accDoc, "three");
|
||||||
|
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(table, false, false),
|
||||||
|
"table should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(row, false, false),
|
||||||
|
"row should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(one, false, false),
|
||||||
|
"one should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(two, false, false),
|
||||||
|
"two should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(three, true, false),
|
||||||
|
"three should be off screen and visible"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ chrome: true, iframe: true, remoteIframe: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test rows and cells outside of the viewport are reported as offscreen.
|
||||||
|
*/
|
||||||
|
addAccessibleTask(
|
||||||
|
`
|
||||||
|
<table id="table" style="height:150vh;" border><tr style="height:100vh;" id="rowA"><td id="one">one</td></tr><tr id="rowB"><td id="two">two</td></tr></table>
|
||||||
|
`,
|
||||||
|
async function(browser, accDoc) {
|
||||||
|
const table = findAccessibleChildByID(accDoc, "table");
|
||||||
|
const rowA = findAccessibleChildByID(accDoc, "rowA");
|
||||||
|
const one = findAccessibleChildByID(accDoc, "one");
|
||||||
|
const rowB = findAccessibleChildByID(accDoc, "rowB");
|
||||||
|
const two = findAccessibleChildByID(accDoc, "two");
|
||||||
|
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(table, false, false),
|
||||||
|
"table should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(rowA, false, false),
|
||||||
|
"rowA should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(one, false, false),
|
||||||
|
"one should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(rowB, true, false),
|
||||||
|
"rowB should be off screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(two, true, false),
|
||||||
|
"two should be off screen and visible"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ chrome: true, iframe: true, remoteIframe: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
addAccessibleTask(
|
||||||
|
`
|
||||||
|
<div id="div">hello</div>
|
||||||
|
`,
|
||||||
|
async function(browser, accDoc) {
|
||||||
|
let textLeaf = findAccessibleChildByID(accDoc, "div").firstChild;
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(textLeaf, false, false),
|
||||||
|
"text should be on screen and visible"
|
||||||
|
);
|
||||||
|
let p = waitForEvent(EVENT_TEXT_INSERTED, "div");
|
||||||
|
await invokeContentTask(browser, [], () => {
|
||||||
|
content.document.getElementById("div").textContent = "goodbye";
|
||||||
|
});
|
||||||
|
await p;
|
||||||
|
textLeaf = findAccessibleChildByID(accDoc, "div").firstChild;
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(textLeaf, false, false),
|
||||||
|
"text should be on screen and visible"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ chrome: true, iframe: true, remoteIframe: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overlapping, opaque divs with the same bounds should not be considered
|
||||||
|
* offscreen.
|
||||||
|
*/
|
||||||
|
addAccessibleTask(
|
||||||
|
`
|
||||||
|
<style>div { height: 5px; width: 5px; background: green; }</style>
|
||||||
|
<div id="outer" role="group"><div style="background:blue;" id="inner" role="group">hi</div></div>
|
||||||
|
`,
|
||||||
|
async function(browser, accDoc) {
|
||||||
|
const outer = findAccessibleChildByID(accDoc, "outer");
|
||||||
|
const inner = findAccessibleChildByID(accDoc, "inner");
|
||||||
|
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(outer, false, false),
|
||||||
|
"outer should be on screen and visible"
|
||||||
|
);
|
||||||
|
await untilCacheOk(
|
||||||
|
() => testVisibility(inner, false, false),
|
||||||
|
"inner should be on screen and visible"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ chrome: true, iframe: true, remoteIframe: true }
|
||||||
|
);
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
/* exported waitForIFrameA11yReady, waitForIFrameUpdates, spawnTestStates */
|
/* exported waitForIFrameA11yReady, waitForIFrameUpdates, spawnTestStates, testVisibility */
|
||||||
|
|
||||||
// Load the shared-head file first.
|
// Load the shared-head file first.
|
||||||
/* import-globals-from ../shared-head.js */
|
/* import-globals-from ../shared-head.js */
|
||||||
@@ -15,9 +15,13 @@ Services.scriptloader.loadSubScript(
|
|||||||
|
|
||||||
// Loading and common.js from accessible/tests/mochitest/ for all tests, as
|
// Loading and common.js from accessible/tests/mochitest/ for all tests, as
|
||||||
// well as promisified-events.js.
|
// well as promisified-events.js.
|
||||||
|
/* import-globals-from ../../mochitest/states.js */
|
||||||
|
/* import-globals-from ../../mochitest/role.js */
|
||||||
loadScripts(
|
loadScripts(
|
||||||
{ name: "common.js", dir: MOCHITESTS_DIR },
|
{ name: "common.js", dir: MOCHITESTS_DIR },
|
||||||
{ name: "promisified-events.js", dir: MOCHITESTS_DIR }
|
{ name: "promisified-events.js", dir: MOCHITESTS_DIR },
|
||||||
|
{ name: "role.js", dir: MOCHITESTS_DIR },
|
||||||
|
{ name: "states.js", dir: MOCHITESTS_DIR }
|
||||||
);
|
);
|
||||||
|
|
||||||
// This is another version of addA11yLoadEvent for fission.
|
// This is another version of addA11yLoadEvent for fission.
|
||||||
@@ -79,3 +83,10 @@ async function spawnTestStates(browsingContext, elementId, expectedStates) {
|
|||||||
testStates
|
testStates
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testVisibility(acc, shouldBeOffscreen, shouldBeInvisible) {
|
||||||
|
const [states] = getStates(acc);
|
||||||
|
let looksGood = shouldBeOffscreen == ((states & STATE_OFFSCREEN) != 0);
|
||||||
|
looksGood &= shouldBeInvisible == ((states & STATE_INVISIBLE) != 0);
|
||||||
|
return looksGood;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1320,6 +1320,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||||||
var rootBounds = Rect()
|
var rootBounds = Rect()
|
||||||
rootNode.getBoundsInScreen(rootBounds)
|
rootNode.getBoundsInScreen(rootBounds)
|
||||||
assertThat("Root node bounds are not empty", rootBounds.isEmpty, equalTo(false))
|
assertThat("Root node bounds are not empty", rootBounds.isEmpty, equalTo(false))
|
||||||
|
assertThat("Root node is visible to user", rootNode.isVisibleToUser, equalTo(true))
|
||||||
|
|
||||||
var labelBounds = Rect()
|
var labelBounds = Rect()
|
||||||
val labelNode = createNodeInfo(rootNode.getChildId(0))
|
val labelNode = createNodeInfo(rootNode.getChildId(0))
|
||||||
@@ -1328,11 +1329,13 @@ class AccessibilityTest : BaseSessionTest() {
|
|||||||
assertThat("Label bounds are in parent", rootBounds.contains(labelBounds), equalTo(true))
|
assertThat("Label bounds are in parent", rootBounds.contains(labelBounds), equalTo(true))
|
||||||
assertThat("First node is a label", labelNode.className.toString(), equalTo("android.view.View"))
|
assertThat("First node is a label", labelNode.className.toString(), equalTo("android.view.View"))
|
||||||
assertThat("Label has text", labelNode.text.toString(), equalTo("Name:"))
|
assertThat("Label has text", labelNode.text.toString(), equalTo("Name:"))
|
||||||
|
assertThat("Label node is visible to user", labelNode.isVisibleToUser, equalTo(true))
|
||||||
|
|
||||||
val entryNode = createNodeInfo(rootNode.getChildId(1))
|
val entryNode = createNodeInfo(rootNode.getChildId(1))
|
||||||
assertThat("Second node is an entry", entryNode.className.toString(), equalTo("android.widget.EditText"))
|
assertThat("Second node is an entry", entryNode.className.toString(), equalTo("android.widget.EditText"))
|
||||||
assertThat("Entry has vieIdwResourceName of 'name'", entryNode.viewIdResourceName, equalTo("name"))
|
assertThat("Entry has vieIdwResourceName of 'name'", entryNode.viewIdResourceName, equalTo("name"))
|
||||||
assertThat("Entry value is text", entryNode.text.toString(), equalTo("Julie"))
|
assertThat("Entry value is text", entryNode.text.toString(), equalTo("Julie"))
|
||||||
|
assertThat("Entry node is visible to user", entryNode.isVisibleToUser, equalTo(true))
|
||||||
if (Build.VERSION.SDK_INT >= 19) {
|
if (Build.VERSION.SDK_INT >= 19) {
|
||||||
assertThat("Entry hint is label",
|
assertThat("Entry hint is label",
|
||||||
entryNode.extras.getString("AccessibilityNodeInfo.hint"),
|
entryNode.extras.getString("AccessibilityNodeInfo.hint"),
|
||||||
@@ -1346,6 +1349,7 @@ class AccessibilityTest : BaseSessionTest() {
|
|||||||
// The child text leaf is pruned, so this button is childless.
|
// The child text leaf is pruned, so this button is childless.
|
||||||
assertThat("Button has a single text leaf", buttonNode.childCount, equalTo(0))
|
assertThat("Button has a single text leaf", buttonNode.childCount, equalTo(0))
|
||||||
assertThat("Button has correct text", buttonNode.text.toString(), equalTo("Submit"))
|
assertThat("Button has correct text", buttonNode.text.toString(), equalTo("Submit"))
|
||||||
|
assertThat("Button is visible to user", buttonNode.isVisibleToUser, equalTo(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test fun testLoadUnloadIframeDoc() {
|
@Test fun testLoadUnloadIframeDoc() {
|
||||||
|
|||||||
Reference in New Issue
Block a user