Bug 1794974: Part 7: Add caching granularity tests, r=Jamie

This revision implements the caching granularity tests. To do this, first it
adds some infrastructure to the accessibleTask function that allows a user of
addAccessibleTask to specify cache domains for the test. If they don't specify
cache domains, all domains are enabled. This way, existing mochitests and browser
tests can keep working with no modifications.

This revision adds utility functions for verifying the absence and presence of a
cache key, with a query function that pokes an accessible like an AT would in
order to trigger caching some domain.

Finally, this revision adds tests for every cache key in every cache domain. The
structure of the tests are: verify the attribute isn't cached (if possible),
then poke the acc like an AT to trigger caching, then verify the attribute has
been cached.

Differential Revision: https://phabricator.services.mozilla.com/D220041
This commit is contained in:
Nathan LaPre 2024-09-09 23:02:21 +00:00
parent 50578506c1
commit 385383c90c
23 changed files with 1300 additions and 0 deletions

View File

@ -37,6 +37,7 @@ BROWSER_CHROME_MANIFESTS += [
"tests/browser/atk/browser.toml",
"tests/browser/bounds/browser.toml",
"tests/browser/browser.toml",
"tests/browser/caching_granularity/browser.toml",
"tests/browser/e10s/browser.toml",
"tests/browser/events/browser.toml",
"tests/browser/fission/browser.toml",

View File

@ -0,0 +1,47 @@
[DEFAULT]
subsuite = "a11y"
support-files = [
"head.js",
"!/accessible/tests/browser/shared-head.js",
"!/accessible/tests/mochitest/*.js",
"!/accessible/tests/mochitest/letters.gif",
]
["browser_actions_domain.js"]
["browser_aria_domain.js"]
["browser_bounds_domain.js"]
["browser_dom_node_id_and_class_domain.js"]
["browser_initial_caching.js"]
["browser_inner_html_caching.js"]
skip-if = ["os != 'win'"]
["browser_name_and_description_domain.js"]
["browser_relations_domain_1.js"]
["browser_relations_domain_2.js"]
["browser_scroll_position_domain.js"]
["browser_text_offset_attributes_domain.js"]
["browser_state_domain.js"]
["browser_style_domain.js"]
["browser_table_domain.js"]
["browser_text_bounds_domain.js"]
["browser_text_domain.js"]
["browser_transform_matrix_domain.js"]
["browser_value_domain.js"]
["browser_viewport_domain.js"]

View File

@ -0,0 +1,56 @@
/* 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";
// CacheKey::AccessKey, CacheDomain::Actions
addAccessibleTask(
`<div id="test" accesskey="x">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "accesskey", () => {
acc.accessKey;
});
},
{
topLevel: true,
iframe: false, // AccessKey issues with iframe - See Bug 1796846
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::HasLongdesc, CacheDomain::Actions
addAccessibleTask(
`<img id="test" src="http://example.com/a11y/accessible/tests/mochitest/moz.png" longdesc="http://example.com">`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "longdesc", () => {
acc.actionCount;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::PrimaryAction, CacheDomain::Actions
addAccessibleTask(
`<button id="test" onclick="console.log('test');">test</button>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "action", () => {
acc.actionCount;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,22 @@
/* 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";
// CacheKey::ARIAAttributes, CacheDomain::ARIA
addAccessibleTask(
`<div id="test" aria-live="polite">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "aria", () => {
acc.attributes;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,67 @@
/* 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";
// CacheDomain::Bounds, CacheKey::CrossDocOffset
addAccessibleTask(
`<div id="test">test</div>`,
async function (browser, _, docAcc) {
// Translate the iframe, which should modify cross-process offset.
info("Setting the transform on the iframe");
await SpecialPowers.spawn(browser, [DEFAULT_IFRAME_ID], iframeID => {
let elm = content.document.getElementById(iframeID);
elm.style.transform = "translate(100px,100px)";
});
await waitForContentPaint(browser);
let acc = findAccessibleChildByID(docAcc, DEFAULT_IFRAME_ID);
await testAttributeCachePresence(acc, "crossorigin", () => {
// Querying bounds queries the CrossDocOffset info.
acc.getBounds({}, {}, {}, {});
});
},
{
topLevel: false,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::IsClipped, CacheDomain::Bounds
addAccessibleTask(
`<div id="generic"><span aria-hidden="true" id="visible">Mozilla</span><span id="invisible" style="display: block !important;border: 0 !important;clip: rect(0 0 0 0) !important;height: 1px !important;margin: -1px !important;overflow: hidden !important;padding: 0 !important;position: absolute !important;white-space: nowrap !important;width: 1px !important;">Mozilla</span><br>I am some other text</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "invisible");
await testAttributeCachePresence(acc, "clip-rule", () => {
// Querying bounds queries the IsClipped info.
acc.getBounds({}, {}, {}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::ParentRelativeBounds, CacheDomain::Bounds
addAccessibleTask(
`<br><div id="test">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "relative-bounds", () => {
// Querying bounds queries the ParentRelativeBounds info.
acc.getBounds({}, {}, {}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,39 @@
/* 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";
// NOTE: These aren't testable easily because the DOMNodeIDAndClass domain is
// required in order to instantiate an accessible task. So, we test only that
// the attributes are present in the cache as expected.
// CacheKey::DOMNodeClass, CacheDomain::DOMNodeIDAndClass
addAccessibleTask(
`<div id="test" class="test">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
verifyAttributeCachedNoRetry(acc, "class");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.DOMNodeIDAndClass,
}
);
// CacheKey::DOMNodeID, CacheDomain::DOMNodeIDAndClass
addAccessibleTask(
`<div id="test" class="test">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
verifyAttributeCachedNoRetry(acc, "id");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.DOMNodeIDAndClass,
}
);

View File

@ -0,0 +1,83 @@
/* 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";
// CacheKey::InputType, no cache domain
addAccessibleTask(
`<input id="test" type="search"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await verifyAttributeCachedNoRetry(acc, "text-input-type");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::MimeType, no cache domain
addAccessibleTask(
``,
async function (browser, docAcc) {
await verifyAttributeCachedNoRetry(docAcc, "content-type");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::PopupType, no cache domain
addAccessibleTask(
`<div popover id="test">test</div>`,
async function (browser, _) {
info("Showing popover");
let shown = waitForEvent(EVENT_SHOW, "test");
await invokeContentTask(browser, [], () => {
content.document.getElementById("test").showPopover();
});
let popover = (await shown).accessible;
await verifyAttributeCachedNoRetry(popover, "ispopup");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::TagName, no cache domain
addAccessibleTask(
``,
async function (browser, docAcc) {
await verifyAttributeCachedNoRetry(docAcc, "tag");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::ARIARole, no cache domain
addAccessibleTask(
`<div id="test" role="invalid-role">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await verifyAttributeCachedNoRetry(acc, "role");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,22 @@
/* 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";
// CacheKey::InnerHTML, CacheDomain::InnerHTML
addAccessibleTask(
`<math id="test"><mfrac><mi>x</mi><mi>y</mi></mfrac></math>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await verifyAttributeCachedNoRetry(acc, "html");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
// Entire test runs with domain already active because there's no XPC method
// to trigger caching of InnerHTML.
cacheDomains: CacheDomain.InnerHTML,
}
);

View File

@ -0,0 +1,69 @@
/* 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";
// NOTE: These aren't testable easily because the NameAndDescription domain is
// required in order to instantiate an accessible task. So, we test only that
// the attributes are present in the cache as expected.
// CacheKey::Description, CacheDomain::NameAndDescription
addAccessibleTask(
`<div id="test" aria-description="test">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
verifyAttributeCachedNoRetry(acc, "description");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.NameAndDescription,
}
);
// CacheKey::HTMLPlaceholder, CacheDomain::NameAndDescription
addAccessibleTask(
`<input type="text" aria-label="label" id="test" placeholder="test"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
verifyAttributeCachedNoRetry(acc, "placeholder");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.NameAndDescription,
}
);
// CacheKey::Name, CacheDomain::NameAndDescription
addAccessibleTask(
`<div id="test" aria-label="name">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
verifyAttributeCachedNoRetry(acc, "name");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.NameAndDescription,
}
);
// CacheKey::NameValueFlag, CacheDomain::NameAndDescription
addAccessibleTask(
`<h3 id="test"><p>test</p></h3>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
verifyAttributeCachedNoRetry(acc, "explicit-name");
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.NameAndDescription,
}
);

View File

@ -0,0 +1,89 @@
/* 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";
// CacheKey::DOMName, CacheDomain::Relations
addAccessibleTask(
`<input id="test" type="radio" name="test"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "attributeName", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// The following tests are for cache keys that are actually relation keys.
// They don't have a CacheKey:: prefix but are cached nonetheless. We don't
// test reverse relations here since they're not stored in the cache.
// RelationType::LABELLEDBY, CacheDomain::Relations
addAccessibleTask(
`
<button aria-labelledby="label" id="labelled">test</button>
<label id="label">button label</label>
`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "labelled");
await testAttributeCachePresence(acc, "labelledby", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// RelationType::LABEL_FOR, CacheDomain::Relations
addAccessibleTask(
`
<button id="button">test</button>
<label for="button" id="label">button label</label>
`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "label");
await testAttributeCachePresence(acc, "for", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// RelationType::CONTROLLER_FOR, CacheDomain::Relations
addAccessibleTask(
`
<button aria-controls="controlled" id="controller">test</button>
<div id="controlled">test</div>
`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "controller");
await testAttributeCachePresence(acc, "controls", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// Continued in browser_relations_domain_2.js.
// Split up to avoid timeouts in test-verify.

View File

@ -0,0 +1,108 @@
/* 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";
// Continued from browser_relations_domain_1.js.
// Split up to avoid timeouts in test-verify.
// RelationType::CONTROLLED_BY, CacheDomain::Relations
addAccessibleTask(
`
<button id="controller">test</button>
<output id="controlled" for="controller">test</div>
`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "controlled");
await testAttributeCachePresence(acc, "for", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// RelationType::DESCRIBED_BY, CacheDomain::Relations
addAccessibleTask(
`
<button aria-describedby="label" id="button">test</button>
<label id="label">button label</label>
`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "button");
await testAttributeCachePresence(acc, "describedby", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// RelationType::FLOWS_TO, CacheDomain::Relations
addAccessibleTask(
`
<div id="flowto" aria-flowto="flowfrom">flow to</div>
<div id="flowfrom">flow from</div>
`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "flowto");
await testAttributeCachePresence(acc, "flowto", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// RelationType::DETAILS, CacheDomain::Relations
addAccessibleTask(
`
<input id="has_details" aria-details="details"/>
<div id="details"></div>
`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "has_details");
await testAttributeCachePresence(acc, "details", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// RelationType::ERRORMSG, CacheDomain::Relations
addAccessibleTask(
`
<input id="has_error" aria-errormessage="error">
<div id="error"></div>
`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "has_error");
await testAttributeCachePresence(acc, "errormessage", () => {
acc.getRelationByType(0);
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,21 @@
/* 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";
// CacheKey::ScrollPosition, CacheDomain::ScrollPosition
addAccessibleTask(
`<div id="test" style="height:200vh;"></div>`,
async function (browser, docAcc) {
await testAttributeCachePresence(docAcc, "scroll-position", () => {
docAcc.getBounds({}, {}, {}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,39 @@
/* 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";
// CacheKey::ARIASelected, CacheDomain::State
addAccessibleTask(
`<div id="test" role="tab" aria-selected="true"></div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "selected", () => {
acc.getState({}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::State, CacheDomain::State
addAccessibleTask(
`<div id="test"></div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "state", () => {
acc.getState({}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,73 @@
/* 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";
// CacheKey::CSSDisplay, CacheDomain::Style
addAccessibleTask(
`<div id="test" style="display:flex;">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "display", () => {
acc.attributes;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::CSSOverflow, CacheDomain::Style
addAccessibleTask(
`<div id="test" style="overflow:hidden;">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "overflow", () => {
acc.getChildAtPoint({}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::CssPosition, CacheDomain::Style
addAccessibleTask(
`<div id="test" style="position:fixed;">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "position", () => {
acc.getChildAtPoint({}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::Opacity, CacheDomain::Style
addAccessibleTask(
`<div id="test" style="opacity:0.5;">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "opacity", () => {
acc.getState({}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,91 @@
/* 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 tableMarkup = `
<table>
<thead>
<tr><th id="header">a</th><th id="colspan-test" colspan="2">b</th></tr>
</thead>
<tbody>
<tr><th id="rowspan-test" rowspan="2">c</th><td rowspan="0">d</td><td>d</td></tr>
<tr><td id="headers-test" headers="header">f</td></tr>
</tbody>
</table>
`;
// CacheKey::CellHeaders, CacheDomain::Table
addAccessibleTask(
tableMarkup,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "headers-test", [
nsIAccessibleTableCell,
]);
await testCachingPerPlatform(acc, "headers", () => {
acc.columnHeaderCells;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::ColSpan, CacheDomain::Table
addAccessibleTask(
tableMarkup,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "colspan-test", [
nsIAccessibleTableCell,
]);
await testCachingPerPlatform(acc, "colspan", () => {
acc.columnExtent;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::RowSpan, CacheDomain::Table
addAccessibleTask(
tableMarkup,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "rowspan-test", [
nsIAccessibleTableCell,
]);
await testCachingPerPlatform(acc, "rowspan", () => {
acc.rowExtent;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::TableLayoutGuess, CacheDomain::Table
addAccessibleTask(
`<table id="test"><tr><td>a</td></tr></table>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testCachingPerPlatform(acc, "layout-guess", () => {
acc.attributes;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,39 @@
/* 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";
// CacheKey::TextBounds, CacheDomain::TextBounds
addAccessibleTask(
`<div id="test">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test").firstChild;
await testAttributeCachePresence(acc, "characterData", () => {
acc.getChildAtPoint({}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::TextLineStarts, CacheDomain::TextBounds
addAccessibleTask(
`<div id="test">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test").firstChild;
await testAttributeCachePresence(acc, "line", () => {
acc.getChildAtPoint({}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,59 @@
/* 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";
// CacheKey::Language, CacheDomain::Text
addAccessibleTask(
`<input id="test" type="radio" lang="fr"/>`,
async function (browser, docAcc) {
console.log("before findAccessibleChildByID");
let acc = findAccessibleChildByID(docAcc, "test");
await testCachingPerPlatform(acc, "language", () => {
acc.language;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::Text, CacheDomain::Text
addAccessibleTask(
`<div id="test">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test").firstChild;
await testCachingPerPlatform(acc, "text", () => {
acc.name;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::TextAttributes, CacheDomain::Text
addAccessibleTask(
`<div id="test">test</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test").firstChild;
await testCachingPerPlatform(acc, "style", () => {
// This is a bit of a shortcut to TextAttributes because querying
// the TextAttributes key directly is difficult to do simply.
acc.language;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,29 @@
/* 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";
// CacheKey::Language, CacheDomain::TextOffsetAttributes | Text
// Cache population triggered via TextOffsetAttributes domain query.
addAccessibleTask(
`<div id="test" contenteditable spellcheck="true">misspelld txt</div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test", [nsIAccessibleText]);
// Just focusing the text should not trigger cache presence of
// TextOffsetAttributes. The AT must request it directly.
info("Focusing the misspelled text.");
acc.takeFocus();
await waitForEvent(EVENT_TEXT_ATTRIBUTE_CHANGED);
let textLeaf = acc.firstChild;
await testAttributeCachePresence(textLeaf, "spelling", () => {
acc.getTextAttributes({}, {}, {}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,22 @@
/* 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";
// CacheKey::TransformMatrix, CacheDomain::TransformMatrix
addAccessibleTask(
`<div id="test" style="transform:translate(100px, 100px);"></div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "transform", () => {
acc.getBounds({}, {}, {}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,124 @@
/* 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";
// CacheKey::MaxValue, CacheDomain::Value
addAccessibleTask(
`<input id="test" type="range" min="0" max="100" value="50"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test", [nsIAccessibleValue]);
await testAttributeCachePresence(acc, "max", () => {
acc.maximumValue;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::MinValue, CacheDomain::Value
addAccessibleTask(
`<input id="test" type="range" min="0" max="100" value="50"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test", [nsIAccessibleValue]);
await testAttributeCachePresence(acc, "min", () => {
acc.minimumValue;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::NumericValue, CacheDomain::Value
addAccessibleTask(
`<input id="test" type="range" min="0" max="100" value="50"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test", [nsIAccessibleValue]);
await testAttributeCachePresence(acc, "value", () => {
acc.currentValue;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::SrcURL, CacheDomain::Value
addAccessibleTask(
`<img id="test" src="image.jpg"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "src", () => {
acc.attributes;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::Step, CacheDomain::Value
addAccessibleTask(
`<input id="test" type="range" min="0" max="100" value="50"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test", [nsIAccessibleValue]);
await testAttributeCachePresence(acc, "step", () => {
acc.minimumIncrement;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::TextValue, CacheDomain::Value
addAccessibleTask(
`<div id="test" role="slider" aria-valuetext="value"></div>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "valuetext", () => {
acc.value;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);
// CacheKey::ValueRegion, CacheDomain::Value
addAccessibleTask(
`<meter id="test"/>`,
async function (browser, docAcc) {
let acc = findAccessibleChildByID(docAcc, "test");
await testAttributeCachePresence(acc, "valuetype", () => {
acc.value;
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,23 @@
/* 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";
// CacheKey::Viewport, CacheDomain::Viewport
addAccessibleTask(
`<div id="test">test</div>`,
async function (browser, docAcc) {
// On Linux, the viewport cache is populated on the DocAccessible without
// explicitly requesting it and is present once the document has loaded.
await testCachingPerPlatform(docAcc, "viewport", () => {
docAcc.getState({}, {});
});
},
{
topLevel: true,
iframe: true,
remoteIframe: true,
cacheDomains: CacheDomain.None,
}
);

View File

@ -0,0 +1,137 @@
/* 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";
/* exported verifyAttributeCached, verifyAttributeCachedNoRetry,
testAttributeCachePresence, testCachingPerPlatform */
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
this
);
// Loading and common.js from accessible/tests/mochitest/ for all tests, as
// well as events.js.
loadScripts(
{ name: "common.js", dir: MOCHITESTS_DIR },
{ name: "layout.js", dir: MOCHITESTS_DIR },
{ name: "promisified-events.js", dir: MOCHITESTS_DIR }
);
/**
* Verifies that the given attribute is cached on the given acc. Retries until
* a timeout via untilCacheOk.
* @param {nsIAccessible} accessible the accessible where the attribute to query
* should be cached
* @param {String} attribute the attribute to query in the cache
*/
async function verifyAttributeCached(accessible, attribute) {
// Wait until the attribute is present in the cache.
await untilCacheOk(() => {
try {
accessible.cache.getStringProperty(attribute);
return true;
} catch (e) {
return false;
}
}, `${attribute} is present in the cache`);
}
/**
* Verifies that the given attribute is cached on the given acc. Doesn't retry
* until a timeout.
* @param {nsIAccessible} accessible the accessible where the attribute to query
* should be cached
* @param {String} attribute the attribute to query in the cache
*/
function verifyAttributeCachedNoRetry(accessible, attribute) {
try {
accessible.cache.getStringProperty(attribute);
ok(true, `${attribute} is present in the cache`);
} catch (e) {
ok(false, `${attribute} is not present in the cache`);
}
}
/*
* @callback QueryCallback A function taking no arguments that queries an
* attribute that may be cached, e.g., bounds, state
*/
/**
* Verifies that the given attribute isn't cached. Then, forces the
* accessibility service to activate those cache domains by running the provided
* query function, which queries the attribute. Finally, verifies that the
* attribute is present in the cache.
* @param {nsIAccessible} accessible the accessible where the attribute to
* query should be cached
* @param {String} attribute the attribute to query in the cache
* @param {QueryCallback} queryCb the callback that this function will
* invoke to query the given attribute
*/
async function testAttributeCachePresence(accessible, attribute, queryCb) {
// Verify that the attribute isn't cached.
let hasAttribute;
try {
accessible.cache.getStringProperty(attribute);
hasAttribute = true;
} catch (e) {
hasAttribute = false;
}
ok(!hasAttribute, `${attribute} is not present in cache`);
// Force attribute to be cached by querying it.
info(`Querying ${attribute} in cache`);
queryCb();
// Wait until the attribute is present in the cache.
await verifyAttributeCached(accessible, attribute);
}
/**
* Verify that the given attribute is properly cached, taking into account
* platform considerations which may affect what is testable. Ideally, test
* attribute absence and presence, but only presence may be possible.
* @param {nsIAccessible} accessible the accessible where the attribute to
* query should be cached
* @param {String} attribute the attribute to query in the cache
* @param {QueryCallback} queryCb the callback that this function will
* invoke to query the given attribute
*/
async function testCachingPerPlatform(accessible, attribute, queryCb) {
// On Linux, the implementation of PlatformEvent for EVENT_NAME_CHANGE calls
// RemoteAccessible::Name during the test setup, which unavoidably queries the
// Text cache domain. Therefore, on Linux we avoid checking for the absence of
// the Text domain attributes. Similarly, we cache the viewport on Linux
// before a test is ready to run.
if (
AppConstants.platform == "linux" &&
(attribute == "language" ||
attribute == "text" ||
attribute == "style" ||
attribute == "viewport")
) {
await verifyAttributeCached(accessible, attribute);
} else if (
AppConstants.platform == "macosx" &&
(attribute == "headers" ||
attribute == "colspan" ||
attribute == "rowspan" ||
attribute == "layout-guess" ||
attribute == "language" ||
attribute == "text" ||
attribute == "style")
) {
// These attributes work on macOS, but aren't consistent. Events may happen
// before document load complete that cause caching before the test starts.
// So, in the (common) event that that doesn't happen, call the queryCb to
// ensure the necessary cache request happens. See bug 1916578.
queryCb();
await verifyAttributeCached(accessible, attribute);
} else {
await testAttributeCachePresence(accessible, attribute, queryCb);
}
}

View File

@ -438,8 +438,45 @@ function snippetToURL(doc, options = {}) {
return url;
}
const CacheDomain = {
None: 0,
NameAndDescription: 0x1 << 0,
Value: 0x1 << 1,
Bounds: 0x1 << 2,
Resolution: 0x1 << 3,
Text: 0x1 << 4,
DOMNodeIDAndClass: 0x1 << 5,
State: 0x1 << 6,
GroupInfo: 0x1 << 7,
Actions: 0x1 << 8,
Style: 0x1 << 9,
TransformMatrix: 0x1 << 10,
ScrollPosition: 0x1 << 11,
Table: 0x1 << 12,
TextOffsetAttributes: 0x1 << 13,
Viewport: 0x1 << 14,
ARIA: 0x1 << 15,
Relations: 0x1 << 16,
InnerHTML: 0x1 << 17,
TextBounds: 0x1 << 18,
All: ~0x0,
};
function accessibleTask(doc, task, options = {}) {
return async function () {
let cacheDomains;
if (!("cacheDomains" in options)) {
cacheDomains = CacheDomain.All;
} else {
// The DOMNodeIDAndClass domain is required for the tests to initialize.
cacheDomains = options.cacheDomains | CacheDomain.DOMNodeIDAndClass;
}
// Set the required cache domains for the test. Note that this also
// instantiates the accessibility service if it hasn't been already, since
// gAccService is defined lazily.
gAccService.setCacheDomains(cacheDomains);
gIsRemoteIframe = options.remoteIframe;
gIsIframe = options.iframe || gIsRemoteIframe;
const urlSuffix = options.urlSuffix || "";
@ -619,6 +656,9 @@ function accessibleTask(doc, task, options = {}) {
* - {String} urlSuffix
* String to append to the document URL. For example, this could be
* "#test" to scroll to the "test" id in the document.
* - {CacheDomain} cacheDomains
* The set of cache domains that should be present at the start of the
* test. If not set, all cache domains will be present.
*/
function addAccessibleTask(doc, task, options = {}) {
const {