Diff
Modified: trunk/LayoutTests/accessibility/aria-modal-multiple-dialogs-expected.txt (295365 => 295366)
--- trunk/LayoutTests/accessibility/aria-modal-multiple-dialogs-expected.txt 2022-06-07 21:31:50 UTC (rev 295365)
+++ trunk/LayoutTests/accessibility/aria-modal-multiple-dialogs-expected.txt 2022-06-07 21:45:25 UTC (rev 295366)
@@ -1,20 +1,46 @@
This tests that aria-modal works correctly on multiple dialogs
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+Verifying the background is accessible on page load.
-PASS backgroundAccessible() is true
-PASS backgroundAccessible() is false
-PASS dialog1Accessible() is true
-PASS backgroundAccessible() is false
-PASS dialog1Accessible() is false
-PASS closeBtn.isIgnored is false
-PASS backgroundAccessible() is false
-PASS dialog1Accessible() is true
-PASS backgroundAccessible() is true
+PASS: background accessible: true
+
+Clicking the display button to open #dialog1.
+
+PASS: background accessible: false
+PASS: #dialog1 accessible: true
+
+Clicking the new button to open #dialog2 without closing #dialog1.
+
+PASS: background accessible: false
+PASS: #dialog1 accessible: false
+PASS: #dialog2 accessible: true
+
+Focusing first descendant of #dialog1.
+
+PASS: background accessible: false
+PASS: #dialog1 accessible: true
+PASS: #dialog2 accessible: false
+
+Moving focus back to first descendant of #dialog2.
+
+PASS: background accessible: false
+PASS: #dialog1 accessible: false
+PASS: #dialog2 accessible: true
+
+Closing dialog2.
+
+PASS: background accessible: false
+PASS: #dialog1 accessible: true
+
+Closing dialog1.
+
+PASS: background accessible: true
+
PASS successfullyParsed is true
TEST COMPLETE
+
Other page content with a dummy focusable element
Display a dialog
Modified: trunk/LayoutTests/accessibility/aria-modal-multiple-dialogs.html (295365 => 295366)
--- trunk/LayoutTests/accessibility/aria-modal-multiple-dialogs.html 2022-06-07 21:31:50 UTC (rev 295365)
+++ trunk/LayoutTests/accessibility/aria-modal-multiple-dialogs.html 2022-06-07 21:45:25 UTC (rev 295366)
@@ -1,94 +1,133 @@
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
-<script src=""
+<script src=""
+<script src=""
</head>
-<style>
-.box-hidden {
- display: none;
-}
-</style>
+<body>
-<body id="body">
+<input type="text" id="textfield" value="Text field.">
+<p id="bgContent">Other page content with <a id="background-link" tabindex="0" href="" dummy focusable element</a></p>
+<p><a _onclick_="toggleDialog(document.getElementById('dialog1'), 'show'); return false;" href="" role="button" id="displayBtn">Display a dialog</a></p>
-<div id="bg">
-<p id="bgContent">Other page content with <a href="" dummy focusable element</a></p>
-<p><a _onclick_="toggleDialog(document.getElementById('box'),'show'); return false;" href="" role="button" id="displayBtn">Display a dialog</a></p>
+<div role="dialog" aria-labelledby="description1" id="dialog1" style="display: none" tabindex="-1">
+ <h3 id="description1">Just an example.</h3>
+ <a id="dialog1-link" tabindex="0" href="" present to ensure we have a focusable element</a>
+ <button id="ok" _onclick_="toggleDialog(document.getElementById('dialog1'), 'hide');">OK</button>
+ <button _onclick_="toggleDialog(document.getElementById('dialog2'), 'show');" id="new">New</button>
</div>
-<div role="dialog" aria-labelledby="myDialog" id="box" class="box-hidden" tabindex="-1">
- <h3 id="myDialog">Just an example.</h3>
- <button id="ok" _onclick_="toggleDialog(document.getElementById('box'),'hide');" class="close-button">OK</button>
- <button _onclick_="toggleDialog(document.getElementById('box2'),'show');" id="new">New</button>
+<div role="dialog" aria-labelledby="description2" id="dialog2" style="display: none" tabindex="-1">
+ <h3 id="description2">Another dialog.</h3>
+ <a id="dialog2-link" tabindex="0" href="" present to ensure we have a focusable element</a>
+ <button id="close" _onclick_="toggleDialog(document.getElementById('dialog2'), 'hide');">Close</button>
</div>
-<div role="dialog" aria-labelledby="myDialog2" id="box2" class="box-hidden" tabindex="-1">
- <h3 id="myDialog2">Another dialog.</h3>
- <button id="close" _onclick_="toggleDialog(document.getElementById('box2'),'hide');" class="close-button">Close</button>
-</div>
+<script>
+ var testOutput = "This tests that aria-modal works correctly on multiple dialogs\n\n";
+ if (window.accessibilityController) {
+ window.jsTestIsAsync = true;
+ setTimeout(async () => {
+ testOutput += "\nVerifying the background is accessible on page load.\n\n";
+ await backgroundAccessible(true);
-<script>
+ testOutput += "\nClicking the display button to open #dialog1.\n\n";
+ document.getElementById("displayBtn").click();
+ await backgroundAccessible(false);
+ await dialog1Accessible(true);
- description("This tests that aria-modal works correctly on multiple dialogs");
+ testOutput += "\nClicking the new button to open #dialog2 without closing #dialog1.\n\n";
+ document.getElementById("new").click();
+ await backgroundAccessible(false);
+ await dialog1Accessible(false);
+ await dialog2Accessible(true);
- if (window.accessibilityController) {
- // Background should be accessible after loading.
- shouldBeTrue("backgroundAccessible()");
-
- // Click the display button, dialog1 shows and background becomes unaccessible.
- document.getElementById("displayBtn").click();
- shouldBeFalse("backgroundAccessible()");
- shouldBeTrue("dialog1Accessible()");
-
- // Click the new button, dialog2 shows and background/dialog1 should both be unaccessible.
- document.getElementById("new").click();
- shouldBeFalse("backgroundAccessible()");
- shouldBeFalse("dialog1Accessible()");
- var closeBtn = accessibilityController.accessibleElementById("close");
- shouldBeFalse("closeBtn.isIgnored");
-
- // Close dialog2, dialog1 should become accessible but not the background
- document.getElementById("close").click();
- shouldBeFalse("backgroundAccessible()");
- shouldBeTrue("dialog1Accessible()");
-
- // Close dialog1, background should be accessible.
- document.getElementById("ok").click();
- shouldBeTrue("backgroundAccessible()");
+ // With both modals active, and focus currently in #dialog2, moving focus to #dialog1 should cause it to become the active modal.
+ testOutput += "\nFocusing first descendant of #dialog1.\n\n";
+ focusFirstDescendant(document.getElementById("dialog1"));
+ await backgroundAccessible(false);
+ await dialog1Accessible(true);
+ await dialog2Accessible(false);
+
+ testOutput += "\nMoving focus back to first descendant of #dialog2.\n\n";
+ focusFirstDescendant(document.getElementById("dialog2"));
+ await backgroundAccessible(false);
+ await dialog1Accessible(false);
+ await dialog2Accessible(true);
+
+ testOutput += "\nClosing dialog2.\n\n";
+ document.getElementById("close").click();
+ await backgroundAccessible(false);
+ await dialog1Accessible(true);
+
+ testOutput += "\nClosing dialog1.\n\n";
+ document.getElementById("ok").click();
+ await backgroundAccessible(true);
+
+ debug(testOutput);
+ finishJSTest();
+ });
}
- function backgroundAccessible() {
- var displayBtn = accessibilityController.accessibleElementById("displayBtn");
- var bgContent = accessibilityController.accessibleElementById("bgContent");
- if (!displayBtn || !bgContent)
- return false;
- return !displayBtn.isIgnored && !bgContent.isIgnored;
+ async function backgroundAccessible(shouldBeAccessible) {
+ await waitFor(() => {
+ const displayBtn = accessibilityController.accessibleElementById("displayBtn");
+ const bgContent = accessibilityController.accessibleElementById("bgContent");
+ if (!displayBtn || !bgContent)
+ return !shouldBeAccessible;
+ return (!displayBtn.isIgnored && !bgContent.isIgnored) === shouldBeAccessible;
+ });
+ testOutput += `PASS: background accessible: ${shouldBeAccessible}\n`
}
- function dialog1Accessible() {
- var okBtn = accessibilityController.accessibleElementById("ok");
- var newBtn = accessibilityController.accessibleElementById("new");
- if (!okBtn || !newBtn)
- return false;
- return !okBtn.isIgnored && !newBtn.isIgnored;
+ async function dialog1Accessible(shouldBeAccessible) {
+ await waitFor(() => {
+ const okBtn = accessibilityController.accessibleElementById("ok");
+ const newBtn = accessibilityController.accessibleElementById("new");
+ if (!okBtn || !newBtn)
+ return !shouldBeAccessible;
+ return (!okBtn.isIgnored && !newBtn.isIgnored) === shouldBeAccessible;
+ });
+ testOutput += `PASS: #dialog1 accessible: ${shouldBeAccessible}\n`
}
+
+ async function dialog2Accessible(shouldBeAccessible) {
+ await waitFor(() => {
+ const closeButton = accessibilityController.accessibleElementById("close");
+ if (!closeButton)
+ return !shouldBeAccessible;
+ return closeButton.isIgnored !== shouldBeAccessible;
+ });
+ testOutput += `PASS: #dialog2 accessible: ${shouldBeAccessible}\n`
+ }
function toggleDialog(dialog, sh) {
- if (sh == "show") {
- // show the dialog
- dialog.style.display = 'block';
- dialog.setAttribute("aria-modal", "true");
- } else {
- dialog.style.display = 'none';
- dialog.setAttribute("aria-modal", "false");
+ if (sh == "show") {
+ dialog.style.display = "block";
+ dialog.setAttribute("aria-modal", "true");
+ // Put focus inside the new dialog so it takes precedence over other dialogs (even if they come later in DOM order).
+ focusFirstDescendant(dialog);
+ } else {
+ dialog.style.display = "none";
+ dialog.setAttribute("aria-modal", "false");
+ }
}
-}
+ function focusFirstDescendant(element) {
+ for (let i = 0; i < element.childNodes.length; i++) {
+ const child = element.childNodes[i];
+ if (!attemptFocus(child))
+ focusFirstDescendant(child)
+ }
+ };
+
+ function attemptFocus(element) {
+ try { element.focus() } catch (e) {}
+ return document.activeElement === element;
+ }
</script>
+</body>
+</html>
-
-<script src=""
-</body>
-</html>
\ No newline at end of file
Added: trunk/LayoutTests/accessibility/recompute-current-modal-after-aria-modal-element-appears-expected.txt (0 => 295366)
--- trunk/LayoutTests/accessibility/recompute-current-modal-after-aria-modal-element-appears-expected.txt (rev 0)
+++ trunk/LayoutTests/accessibility/recompute-current-modal-after-aria-modal-element-appears-expected.txt 2022-06-07 21:45:25 UTC (rev 295366)
@@ -0,0 +1,15 @@
+This test ensures that we update the page-wide active modal when an aria-modal element is dynamically added and removed.
+
+PASS: Modal content is not initially accessible.
+
+Un-hiding aria-modal element.
+PASS: Background is inaccessible, modal content is accessible.
+
+Re-hiding aria-modal element.
+PASS: Background is accessible, modal content is inaccessible.
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
+
Added: trunk/LayoutTests/accessibility/recompute-current-modal-after-aria-modal-element-appears.html (0 => 295366)
--- trunk/LayoutTests/accessibility/recompute-current-modal-after-aria-modal-element-appears.html (rev 0)
+++ trunk/LayoutTests/accessibility/recompute-current-modal-after-aria-modal-element-appears.html 2022-06-07 21:45:25 UTC (rev 295366)
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+
+<div id="content">
+ <p id="p-before-modal">Page content before modal</p>
+
+ <div id="modal" aria-modal="true" role="dialog" style="display: none">
+ <p id="p-inside-modal">Page content inside modal</p>
+ <button id="close-button">Close modal button</button>
+ </div>
+ <div id="new-modal-container"></div>
+
+ <p id="p-after-modal">Page content after modal</p>
+</div>
+
+<script>
+ var testOutput = "This test ensures that we update the page-wide active modal when an aria-modal element is dynamically added and removed.\n\n";
+
+ function backgroundIsAccessible() {
+ return accessibilityController.accessibleElementById("p-before-modal") &&
+ accessibilityController.accessibleElementById("p-after-modal");
+ }
+ function modalContentIsAccessible() {
+ return accessibilityController.accessibleElementById("p-inside-modal") &&
+ accessibilityController.accessibleElementById("close-button");
+ }
+
+ if (window.accessibilityController) {
+ window.jsTestIsAsync = true;
+
+ if (backgroundIsAccessible() && !modalContentIsAccessible())
+ testOutput += "PASS: Modal content is not initially accessible.\n";
+ else
+ testOutput += "FAIL: Model content is initially accessible.\n";
+
+ testOutput += `\nUn-hiding aria-modal element.\n`;
+ // Move the modal in the DOM. This is the key thing being tested -- do we properly preserve modal behavior
+ // for a modal that has been moved?
+ document.getElementById("new-modal-container").appendChild(document.getElementById("modal"))
+ document.getElementById("modal").removeAttribute("style");
+ setTimeout(async function() {
+ await waitFor(() => { return !backgroundIsAccessible() && modalContentIsAccessible() });
+ testOutput += "PASS: Background is inaccessible, modal content is accessible.\n";
+
+ testOutput += `\nRe-hiding aria-modal element.\n`;
+ document.getElementById("modal").style.display = "none";
+ await waitFor(() => { return backgroundIsAccessible() });
+ testOutput += "PASS: Background is accessible, modal content is inaccessible.\n";
+
+ document.getElementById("content").style.visibility = "hidden";
+ debug(testOutput);
+ finishJSTest();
+ }, 0);
+ }
+</script>
+</body>
+</html>
+
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.cpp (295365 => 295366)
--- trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2022-06-07 21:31:50 UTC (rev 295365)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.cpp 2022-06-07 21:45:25 UTC (rev 295366)
@@ -222,7 +222,6 @@
, m_notificationPostTimer(*this, &AXObjectCache::notificationPostTimerFired)
, m_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired)
, m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired)
- , m_focusModalNodeTimer(*this, &AXObjectCache::focusModalNodeTimerFired)
, m_currentModalElement(nullptr)
, m_performCacheUpdateTimer(*this, &AXObjectCache::performCacheUpdateTimerFired)
{
@@ -240,7 +239,6 @@
{
m_notificationPostTimer.stop();
m_liveRegionChangedPostTimer.stop();
- m_focusModalNodeTimer.stop();
m_performCacheUpdateTimer.stop();
for (const auto& object : m_objects.values())
@@ -296,11 +294,24 @@
return false;
}
-Element* AXObjectCache::currentModalNode()
+void AXObjectCache::updateCurrentModalNode()
{
+ auto* previousModal = m_currentModalElement.get();
+ m_currentModalElement = updateCurrentModalNodeInternal();
+ if (previousModal != m_currentModalElement.get()) {
+ childrenChanged(rootWebArea());
+#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
+ // Because the presence of a modal affects every element on the page,
+ // regenerate the entire isolated tree with the next cache update.
+ m_deferredRegenerateIsolatedTree = true;
+#endif
+ }
+}
+
+Element* AXObjectCache::updateCurrentModalNodeInternal()
+{
// There might be multiple modal dialog nodes.
// We use this function to pick the one we want.
- m_currentModalElement = nullptr;
if (m_modalElementsSet.isEmpty())
return nullptr;
@@ -325,17 +336,11 @@
if (!isNodeVisible(element) || !modalElementHasAccessibleContent(*element))
continue;
- if (focusedElement && focusedElement->isDescendantOf(element)) {
- m_currentModalElement = element;
+ lastVisible = element;
+ if (focusedElement && focusedElement->isDescendantOf(element))
break;
- }
- lastVisible = element;
}
-
- if (!m_currentModalElement)
- m_currentModalElement = lastVisible.get();
-
- return m_currentModalElement.get();
+ return lastVisible.get();
}
bool AXObjectCache::isNodeVisible(Node* node) const
@@ -385,7 +390,8 @@
return m_currentModalElement.get();
// Recompute the valid aria modal node when m_currentModalElement is null or hidden.
- return currentModalNode();
+ updateCurrentModalNode();
+ return m_currentModalElement.get();
}
AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement)
@@ -1119,6 +1125,12 @@
m_deferredNodeAddedOrRemovedList.add(node);
+ if (is<Element>(node)) {
+ auto* changedElement = downcast<Element>(node);
+ if (isModalElement(*changedElement))
+ deferModalChange(changedElement);
+ }
+
if (!m_performCacheUpdateTimer.isActive())
m_performCacheUpdateTimer.startOneShot(0_s);
}
@@ -1351,12 +1363,14 @@
m_performCacheUpdateTimer.startOneShot(0_s);
}
-void AXObjectCache::handleFocusedUIElementChanged(Node* oldNode, Node* newNode)
+void AXObjectCache::handleFocusedUIElementChanged(Node* oldNode, Node* newNode, UpdateModal updateModal)
{
#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
setIsolatedTreeFocusedObject(newNode);
#endif
+ if (updateModal == UpdateModal::Yes)
+ updateCurrentModalNode();
handleMenuItemSelected(newNode);
platformHandleFocusedUIElementChanged(oldNode, newNode);
}
@@ -1759,16 +1773,8 @@
return nullptr;
}
-void AXObjectCache::focusModalNode()
+void AXObjectCache::focusCurrentModal()
{
- if (m_focusModalNodeTimer.isActive())
- m_focusModalNodeTimer.stop();
-
- m_focusModalNodeTimer.startOneShot(0_s);
-}
-
-void AXObjectCache::focusModalNodeTimerFired()
-{
if (!m_document.hasLivingRenderTree())
return;
@@ -1775,6 +1781,10 @@
Ref<Document> protectedDocument(m_document);
if (!nodeAndRendererAreValid(m_currentModalElement.get()) || !isNodeVisible(m_currentModalElement.get()))
return;
+
+ // Don't focus the current modal if focus has been requested to be put elsewhere (e.g. via JS).
+ if (!m_deferredFocusedNodeChange.isEmpty())
+ return;
// Don't set focus if we are already focusing onto some element within
// the dialog.
@@ -1979,34 +1989,6 @@
postNotification(element, AXObjectCache::AXSortDirectionChanged);
}
-void AXObjectCache::handleModalChange(Element& element)
-{
- if (!is<HTMLDialogElement>(element) && !nodeHasRole(&element, "dialog"_s) && !nodeHasRole(&element, "alertdialog"_s))
- return;
-
- stopCachingComputedObjectAttributes();
-
- if (!m_modalNodesInitialized)
- findModalNodes();
-
- if (isModalElement(element)) {
- // Add the newly modified node to the modal nodes set.
- // We will recompute the current valid aria modal node in modalNode() when this node is not visible.
- m_modalElementsSet.add(&element);
- } else {
- // Remove the node from the modal nodes set.
- m_modalElementsSet.remove(&element);
- }
-
- // Find new active modal node.
- currentModalNode();
-
- if (m_currentModalElement)
- focusModalNode();
-
- startCachingComputedObjectAttributesUntilTreeMutates();
-}
-
void AXObjectCache::labelChanged(Element* element)
{
ASSERT(is<HTMLLabelElement>(*element));
@@ -3393,23 +3375,60 @@
m_deferredAttributeChange.clear();
for (auto& deferredFocusedChangeContext : m_deferredFocusedNodeChange) {
- handleFocusedUIElementChanged(deferredFocusedChangeContext.first, deferredFocusedChangeContext.second);
+ // Don't recompute the active modal for each individal focus change, as that could cause a lot of expensive tree rebuilding. Instead, we do it once below.
+ handleFocusedUIElementChanged(deferredFocusedChangeContext.first, deferredFocusedChangeContext.second, UpdateModal::No);
// Recompute isIgnored after a focus change in case that altered visibility.
recomputeIsIgnored(deferredFocusedChangeContext.first);
recomputeIsIgnored(deferredFocusedChangeContext.second);
}
+ bool updatedFocusedElement = !m_deferredFocusedNodeChange.isEmpty();
+ // If we changed the focused element, that could affect what modal should be active, so recompute it.
+ bool shouldRecomputeModal = updatedFocusedElement;
m_deferredFocusedNodeChange.clear();
- m_deferredModalChangedList.forEach([this] (auto& deferredModalChangedElement) {
- handleModalChange(deferredModalChangedElement);
- });
+ for (auto& element : m_deferredModalChangedList) {
+ if (!is<HTMLDialogElement>(element) && !nodeHasRole(&element, "dialog"_s) && !nodeHasRole(&element, "alertdialog"_s))
+ continue;
+
+ shouldRecomputeModal = true;
+ if (!m_modalNodesInitialized)
+ findModalNodes();
+
+ if (isModalElement(element)) {
+ // Add the newly modified node to the modal nodes set.
+ // We will recompute the current valid aria modal node in modalNode() when this node is not visible.
+ m_modalElementsSet.add(&element);
+ } else
+ m_modalElementsSet.remove(&element);
+ }
m_deferredModalChangedList.clear();
+ if (shouldRecomputeModal) {
+ updateCurrentModalNode();
+ // "When a modal element is displayed, assistive technologies SHOULD navigate to the element unless focus has explicitly been set elsewhere."
+ // `updatedFocusedElement` indicates focus was explicitly set elsewhere, so don't autofocus into the modal.
+ // https://w3c.github.io/aria/#aria-modal
+ if (!updatedFocusedElement)
+ focusCurrentModal();
+ }
+
m_deferredMenuListChange.forEach([this] (auto& element) {
handleMenuListValueChanged(element);
});
m_deferredMenuListChange.clear();
+#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
+ if (m_deferredRegenerateIsolatedTree && m_pageID) {
+ if (auto tree = AXIsolatedTree::treeForPageID(*m_pageID)) {
+ if (auto* webArea = rootWebArea()) {
+ AXLOG("Regenerating isolated tree from AXObjectCache::performDeferredCacheUpdate().");
+ tree->generateSubtree(*webArea);
+ }
+ }
+ }
+ m_deferredRegenerateIsolatedTree = false;
+#endif
+
platformPerformDeferredCacheUpdate();
}
Modified: trunk/Source/WebCore/accessibility/AXObjectCache.h (295365 => 295366)
--- trunk/Source/WebCore/accessibility/AXObjectCache.h 2022-06-07 21:31:50 UTC (rev 295365)
+++ trunk/Source/WebCore/accessibility/AXObjectCache.h 2022-06-07 21:45:25 UTC (rev 295366)
@@ -342,7 +342,6 @@
void postTextStateChangeNotification(Node*, const AXTextStateChangeIntent&, const VisibleSelection&);
void postTextStateChangeNotification(const Position&, const AXTextStateChangeIntent&, const VisibleSelection&);
void postLiveRegionChangeNotification(AccessibilityObject*);
- void focusModalNode();
enum AXLoadingEvent {
AXLoadingStarted,
@@ -455,7 +454,7 @@
void liveRegionChangedNotificationPostTimerFired();
- void focusModalNodeTimerFired();
+ void focusCurrentModal();
void performCacheUpdateTimerFired();
@@ -481,15 +480,16 @@
void handleActiveDescendantChanged(Element&);
void handleAriaExpandedChange(Node*);
- void handleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode);
+ enum class UpdateModal : bool { No, Yes };
+ void handleFocusedUIElementChanged(Node* oldFocusedNode, Node* newFocusedNode, UpdateModal = UpdateModal::Yes);
void handleMenuListValueChanged(Element&);
// aria-modal or modal <dialog> related
bool isModalElement(Element&) const;
void findModalNodes();
- Element* currentModalNode();
+ void updateCurrentModalNode();
+ Element* updateCurrentModalNodeInternal();
bool isNodeVisible(Node*) const;
- void handleModalChange(Element&);
bool modalElementHasAccessibleContent(Element&);
// Relationships between objects.
@@ -532,7 +532,6 @@
Timer m_liveRegionChangedPostTimer;
ListHashSet<RefPtr<AccessibilityObject>> m_liveRegionObjectsSet;
- Timer m_focusModalNodeTimer;
WeakPtr<Element> m_currentModalElement;
// Multiple aria-modals behavior is undefined by spec. We keep them sorted based on DOM order here.
// If that changes to require only one aria-modal we could change this to a WeakHashSet, or discard the set completely.
@@ -554,6 +553,9 @@
HashMap<Element*, String> m_deferredTextFormControlValue;
HashMap<Element*, QualifiedName> m_deferredAttributeChange;
Vector<std::pair<Node*, Node*>> m_deferredFocusedNodeChange;
+#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
+ bool m_deferredRegenerateIsolatedTree { false };
+#endif
bool m_isSynchronizingSelection { false };
bool m_performingDeferredCacheUpdate { false };
double m_loadingProgress { 0 };
@@ -588,7 +590,7 @@
inline AccessibilityReplacedText::AccessibilityReplacedText(const VisibleSelection&) { }
inline void AccessibilityReplacedText::postTextStateChangeNotification(AXObjectCache*, AXTextEditType, const String&, const VisibleSelection&) { }
inline void AXComputedObjectAttributeCache::setIgnored(AXID, AccessibilityObjectInclusion) { }
-inline AXObjectCache::AXObjectCache(Document& document) : m_document(document), m_notificationPostTimer(*this, &AXObjectCache::notificationPostTimerFired), m_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired), m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired), m_focusModalNodeTimer(*this, &AXObjectCache::focusModalNodeTimerFired), m_performCacheUpdateTimer(*this, &AXObjectCache::performCacheUpdateTimerFired) { }
+inline AXObjectCache::AXObjectCache(Document& document) : m_document(document), m_notificationPostTimer(*this, &AXObjectCache::notificationPostTimerFired), m_passwordNotificationPostTimer(*this, &AXObjectCache::passwordNotificationPostTimerFired), m_liveRegionChangedPostTimer(*this, &AXObjectCache::liveRegionChangedNotificationPostTimerFired), m_performCacheUpdateTimer(*this, &AXObjectCache::performCacheUpdateTimerFired) { }
inline AXObjectCache::~AXObjectCache() { }
inline AccessibilityObject* AXObjectCache::get(RenderObject*) { return nullptr; }
inline AccessibilityObject* AXObjectCache::get(Node*) { return nullptr; }
@@ -623,19 +625,18 @@
#if !PLATFORM(COCOA) && !USE(ATSPI)
inline void AXObjectCache::detachWrapper(AXCoreObject*, AccessibilityDetachmentType) { }
#endif
-inline void AXObjectCache::focusModalNodeTimerFired() { }
+inline void AXObjectCache::focusCurrentModal() { }
inline void AXObjectCache::performCacheUpdateTimerFired() { }
inline void AXObjectCache::frameLoadingEventNotification(Frame*, AXLoadingEvent) { }
inline void AXObjectCache::frameLoadingEventPlatformNotification(AccessibilityObject*, AXLoadingEvent) { }
inline void AXObjectCache::handleActiveDescendantChanged(Element&) { }
inline void AXObjectCache::handleAriaExpandedChange(Node*) { }
-inline void AXObjectCache::handleModalChange(Element&) { }
inline void AXObjectCache::deferModalChange(Element*) { }
inline void AXObjectCache::handleRoleChange(AccessibilityObject*) { }
inline void AXObjectCache::deferAttributeChangeIfNeeded(const QualifiedName&, Element*) { }
inline void AXObjectCache::handleAttributeChange(const QualifiedName&, Element*) { }
inline bool AXObjectCache::shouldProcessAttributeChange(const QualifiedName&, Element*) { return false; }
-inline void AXObjectCache::handleFocusedUIElementChanged(Node*, Node*) { }
+inline void AXObjectCache::handleFocusedUIElementChanged(Node*, Node*, UpdateModal) { }
inline void AXObjectCache::handleScrollbarUpdate(ScrollView*) { }
inline void AXObjectCache::handleScrolledToAnchor(const Node*) { }
inline void AXObjectCache::liveRegionChangedNotificationPostTimerFired() { }