This patch adds two performance tests to measure the cost of the naive solution to bounds computation for display:contents accessibles against our "normal" bounds ocmputation. Here's some data from a recent try run that shows how this algorithm compares to (a) our basic bounds algorithm and (b) other hot paths in our perf test suite. A summary, to save some time digging into the data: As you might expect, a single `getBounds()` on a `display:contents` container with 10k children costs more than any other tracked per-op cost in the suite. The cost for this operation is ~3–5 milliseconds. The cost for 100 of these queries (the entire test run) is ~300-500 milliseconds. I think this is still worth shipping as a stop gap for the kind of navigation issues VoiceOver experiences as a result. I ALSO think we should dig in further and come up with a more performant situation. Separately: Linux is an outlier on `browser_addManyNodes.js`... what's going on there... Try push: <https://treeherder.mozilla.org/jobs?repo=try&landoInstance=lando-prod-2025&landoCommitID=57011> | Test | linux2404-64-shippable | mac1015-64-shippable-qr | win11-24h2-shippable | win11-24h2-hw-ref-shippable | |---|---:|---:|---:|---:| | `browser_bounds.js` | 0.472 ms | 0.274 ms | 0.289 ms | 0.425 ms | | `browser_displayContentsBounds.js` | 477.690 ms | 554.363 ms | 344.961 ms | 346.721 ms | | Test | linux2404-64-shippable | mac1015-64-shippable-qr | win11-24h2-shippable | win11-24h2-hw-ref-shippable | | ---------------------------------- | ---------------------: | ----------------------: | -------------------: | --------------------------: | | `browser_bounds.js` | 4.72 µs | 2.74 µs | 2.89 µs | 4.25 µs | | `browser_displayContentsBounds.js` | 4776.90 µs | 5543.63 µs | 3449.61 µs | 3467.21 µs | Each perftest runs a hot loop of N operations and reports the total wall time. Dividing total time by N gives an approximate time-per-call cost. | Test | N (ops) | linux | mac CI | win-main | win-ref | |---|---:|---:|---:|---:|---:| | `browser_addManyIds.js` (setAttribute id) | 10,000 | 5.76 µs | 6.65 µs | 3.58 µs | 3.28 µs | | `browser_addManyNodes.js` (append child div) | 100,000 | 246.61 µs | 38.12 µs | 23.45 µs | 23.63 µs | | `browser_bounds.js` (`getBounds` on framed) | 100 | 4.72 µs | 2.74 µs | 2.89 µs | 4.25 µs | | `browser_displayContentsBounds.js` (`getBounds` on display:contents) | 100 | 4,776.90 µs | 5,543.63 µs | 3,449.61 µs | 3,467.21 µs | | `browser_implicitRoleARIAExpanded.js` (append element w/ implicit role) | 10,000 | 22.06 µs | 20.50 µs | 11.40 µs | 13.19 µs | | `browser_reflowPseudoelements.js` (reflow pseudoelement) | 15,000 | 37.06 µs | 46.76 µs | 25.72 µs | 30.65 µs | | `browser_removeManySpellingErrors.js` (remove spell error) | 500 | 168.26 µs | 192.78 µs | 90.61 µs | 116.92 µs | Differential Revision: https://phabricator.services.mozilla.com/D306267
354 lines
10 KiB
JavaScript
354 lines
10 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
// XXX: We can consolidate this once bug 1930955 lands
|
|
// eslint-disable-next-line no-unused-vars
|
|
const perfMetadata = {
|
|
owner: "Accessibility Team",
|
|
name: "browser_displayContentsBounds.js",
|
|
description:
|
|
"Audit a11y performance when computing bounds for a display:contents accessible with 10000 children.",
|
|
options: {
|
|
default: {
|
|
extra_args: ["headless"],
|
|
manifest: "perftest.toml",
|
|
manifest_flavor: "browser-chrome",
|
|
perfherder: true,
|
|
perfherder_metrics: [
|
|
// Total time for the entire run (this probe is not process specific)
|
|
{ name: "A11Y_TotalTime", unit: "ms", shouldAlert: true },
|
|
|
|
//////////////// PARENT PROCESS
|
|
|
|
// Timing metrics
|
|
{ name: "A11Y_DoInitialUpdate_parent", unit: "ms", shouldAlert: false },
|
|
{
|
|
name: "A11Y_ProcessQueuedCacheUpdate_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ContentRemovedNode_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ContentRemovedAcc_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_PruneOrInsertSubtree_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ShutdownChildrenInSubtree_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{ name: "A11Y_ShowEvent_parent", unit: "ms", shouldAlert: false },
|
|
{ name: "A11Y_RecvCache_parent", unit: "ms", shouldAlert: false },
|
|
{
|
|
name: "A11Y_ProcessShowEvent_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{ name: "A11Y_CoalesceEvents_parent", unit: "ms", shouldAlert: false },
|
|
{
|
|
name: "A11Y_CoalesceMutationEvents_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessHideEvent_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{ name: "A11Y_SendCache_parent", unit: "ms", shouldAlert: false },
|
|
{ name: "A11Y_WillRefresh_parent", unit: "ms", shouldAlert: false },
|
|
{
|
|
name: "A11Y_AccessibilityServiceInit_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_PlatformShowHideEvent_parent",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
// Occurrence metrics
|
|
{
|
|
name: "A11Y_DoInitialUpdate_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessQueuedCacheUpdate_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ContentRemovedNode_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ContentRemovedAcc_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_PruneOrInsertSubtree_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ShutdownChildrenInSubtree_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ShowEvent_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_RecvCache_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessShowEvent_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_CoalesceEvents_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_CoalesceMutationEvents_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessHideEvent_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_SendCache_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_WillRefresh_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_AccessibilityServiceInit_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_PlatformShowHideEvent_Count_parent",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
|
|
//////////////// CONTENT PROCESS
|
|
|
|
// Timing metrics
|
|
{
|
|
name: "A11Y_DoInitialUpdate_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessQueuedCacheUpdate_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ContentRemovedNode_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ContentRemovedAcc_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_PruneOrInsertSubtree_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ShutdownChildrenInSubtree_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{ name: "A11Y_ShowEvent_content", unit: "ms", shouldAlert: false },
|
|
{ name: "A11Y_RecvCache_content", unit: "ms", shouldAlert: false },
|
|
{
|
|
name: "A11Y_ProcessShowEvent_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_CoalesceEvents_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_CoalesceMutationEvents_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessHideEvent_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{ name: "A11Y_SendCache_content", unit: "ms", shouldAlert: false },
|
|
{ name: "A11Y_WillRefresh_content", unit: "ms", shouldAlert: false },
|
|
{
|
|
name: "A11Y_AccessibilityServiceInit_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_PlatformShowHideEvent_content",
|
|
unit: "ms",
|
|
shouldAlert: false,
|
|
},
|
|
// Occurrence metrics
|
|
{
|
|
name: "A11Y_DoInitialUpdate_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessQueuedCacheUpdate_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ContentRemovedNode_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ContentRemovedAcc_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_PruneOrInsertSubtree_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ShutdownChildrenInSubtree_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ShowEvent_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_RecvCache_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessShowEvent_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_CoalesceEvents_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_CoalesceMutationEvents_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_ProcessHideEvent_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_SendCache_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_WillRefresh_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_AccessibilityServiceInit_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
{
|
|
name: "A11Y_PlatformShowHideEvent_Count_content",
|
|
unit: "iterations",
|
|
shouldAlert: false,
|
|
},
|
|
],
|
|
try_platform: ["linux", "mac", "win"],
|
|
verbose: true,
|
|
},
|
|
},
|
|
};
|
|
|
|
addAccessibleTask(
|
|
`<div id="container" role="group" style="display:none;"></div>`,
|
|
async function testDisplayContentsBounds(browser, accDoc) {
|
|
info("Adding 10000 children to display:contents container");
|
|
const containerShown = waitForEvent(EVENT_SHOW, "container");
|
|
// We add the nodes to the container while it is hidden to
|
|
// ensure we produce a single show event rather than 10000.
|
|
// This makes the perf comparison more straightforward, i.e.
|
|
// the perf cost of this test isn't drowned out by the show event cost.
|
|
await invokeContentTask(browser, [], () => {
|
|
const container = content.document.getElementById("container");
|
|
for (let c = 0; c < 10000; ++c) {
|
|
const child = content.document.createElement("div");
|
|
child.textContent = c;
|
|
container.append(child);
|
|
}
|
|
container.style.setProperty("display", "contents");
|
|
});
|
|
await containerShown;
|
|
info("Container shown, starting bounds measurement");
|
|
|
|
const container = findAccessibleChildByID(accDoc, "container");
|
|
await timeThis(async () => {
|
|
const x = {};
|
|
const y = {};
|
|
const w = {};
|
|
const h = {};
|
|
for (let i = 0; i < 100; i++) {
|
|
// Query bounds on the container to exercise the child traversal
|
|
// Do this 100 times to exacerbate the effect :)
|
|
container.getBounds(x, y, w, h);
|
|
}
|
|
});
|
|
}
|
|
);
|