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:
Morgan Rae Reschenberg
2022-10-18 22:30:10 +00:00
parent 13803a5ee0
commit bf7515d46c
13 changed files with 379 additions and 136 deletions

View File

@@ -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.

View File

@@ -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; }
/** /**

View File

@@ -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.

View File

@@ -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:

View File

@@ -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;
} }

View File

@@ -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 {

View File

@@ -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

View File

@@ -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);
}
});

View File

@@ -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);

View File

@@ -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 }
); );

View 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 }
);

View File

@@ -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;
}

View File

@@ -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() {