diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt index 4ab4933efa02c..b5a85c5b4d7e3 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindow.web.kt @@ -139,6 +139,8 @@ fun ComposeViewport( position = "absolute" top = "0" left = "0" + width = "100%" + height = "100%" } appContainer.appendChild(a11yContainer) } diff --git a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindowInternal.web.kt b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindowInternal.web.kt index 8e6679c5bf89f..b66052bcc1ef8 100644 --- a/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindowInternal.web.kt +++ b/compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/ComposeWindowInternal.web.kt @@ -501,6 +501,10 @@ internal class ComposeWindow( canvas.style.width = "${boxSize.width.value}px" canvas.style.height = "${boxSize.height.value}px" + // The a11y container is sized via CSS (`width: 100%; height: 100%`) at construction + // time, so it tracks the canvas automatically without a per-resize bridge call across + // the wasm2js boundary. See ComposeViewport for the setup. + _windowInfo.containerSize = sizeInPx _windowInfo.containerDpSize = boxSize diff --git a/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/platform/a11y/A11yContainerSizingTest.kt b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/platform/a11y/A11yContainerSizingTest.kt new file mode 100644 index 0000000000000..ae2a4b82ac5de --- /dev/null +++ b/compose/ui/ui/src/webTest/kotlin/androidx/compose/ui/platform/a11y/A11yContainerSizingTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.ui.platform.a11y + +import androidx.compose.material.Text +import androidx.compose.ui.OnCanvasTests +import kotlin.test.Test +import kotlin.test.assertNotNull +import kotlin.test.assertTrue +import kotlinx.coroutines.test.runTest + +/** + * Regression test for https://youtrack.jetbrains.com/issue/CMP-10172. + * + * The a11y root container (`cmp_a11y_root`) was created with `position: absolute` but never + * given a width or height, leaving it as a 0×0 element in the DOM. Because hit-test-based + * accessibility tools (Apple Accessibility Inspector, Appium) walk down from the parent's + * bounding rect, they could not reach any Compose semantic node. VoiceOver was unaffected + * because it traverses the DOM tree sequentially, which masked the bug. + */ +class A11yContainerSizingTest : OnCanvasTests { + + @Test + fun a11yContainerHasNonZeroRenderedSizeAfterInit() = runTest { + createComposeWindow { + Text("a11y sizing regression") + } + + val a11yContainer = assertNotNull( + getA11YContainer(), + "A11Y container must exist when isA11YEnabled is true (default)" + ) + + val rect = a11yContainer.getBoundingClientRect() + + assertTrue( + rect.width > 0.0, + "a11y container rendered width must be non-zero, was ${rect.width}" + ) + assertTrue( + rect.height > 0.0, + "a11y container rendered height must be non-zero, was ${rect.height}" + ) + } +}