AX ITM: Defer to the tree when determining AX object loading progress
https://bugs.webkit.org/show_bug.cgi?id=235646

Reviewed by Andres Gonzalez.

Source/WebCore:

Currently, we set AXPropertyName::IsLoaded and AXPropertyName::EstimatedLoadingProgress
once in AXIsolatedObject::initializeAttributeData and never update it. This means that
if an object is created while the page is at some non-100% load percentage, AX clients
that request these attributes will always think the object isn't loaded.

This patch removes these two properties and instead defers to the isolated tree associated
with each object when determining the load percentage. Both of these properties were based
on the entire page / document load progress anyways, so it doesn't make sense to set the
same value for all of them.

We track loading progress by changing ProgressTracker to also notify
the page when progress changes, which in turn updates the associated
AXObjectCache with the new progress value.

Covered by accessibility/mac/document-attributes.html which verifies the AXLoad and
AXLoadingProgress attributes.

* accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::AXObjectCache):
(WebCore::AXObjectCache::updateLoadingProgress):
Added.
* accessibility/AXObjectCache.h:
(WebCore::AXObjectCache::loadingFinished):
(WebCore::AXObjectCache::loadingProgress const):
Added.
* accessibility/AccessibilityObject.h:
* accessibility/AccessibilityObjectInterface.h:
* accessibility/AccessibilityRenderObject.cpp:
(WebCore::AccessibilityRenderObject::loadingProgress const):
(WebCore::AccessibilityRenderObject::estimatedLoadingProgress const):
Rename estimatedLoadingProgress methods to loadingProgress.
* accessibility/AccessibilityRenderObject.h:
* accessibility/isolatedtree/AXIsolatedObject.cpp:
(WebCore::AXIsolatedObject::initializeAttributeData):
* accessibility/isolatedtree/AXIsolatedObject.h:
* accessibility/isolatedtree/AXIsolatedTree.cpp:
(WebCore::AXIsolatedTree::create):
(WebCore::AXIsolatedTree::updateLoadingProgress):
(WebCore::AXIsolatedTree::applyPendingChanges):
Apply m_pendingEstimatedLoadingProgress.
* accessibility/isolatedtree/AXIsolatedTree.h:
(WebCore::AXIsolatedTree::loadingProgress):
Added.
* accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
(-[WebAccessibilityObjectWrapper accessibilityAttributeValue:]):
Handle rename of estimatedLoadingProgress methods to loadingProgress.
* loader/ProgressTracker.cpp:
(WebCore::ProgressTracker::ProgressTracker):
Store a reference to the associated page in new m_page member.
(WebCore::ProgressTracker::progressStarted):
(WebCore::ProgressTracker::progressEstimateChanged):
(WebCore::ProgressTracker::finalProgressComplete):
(WebCore::ProgressTracker::incrementProgress):
Notify the Page when progress changes or completes.
* loader/ProgressTracker.h:
* page/Page.cpp:
(WebCore::Page::Page):
(WebCore::Page::progressEstimateChanged const):
Added.
(WebCore::Page::progressFinished const):
Added.
* page/Page.h:

LayoutTests:

* accessibility/mac/document-attributes-expected.txt:
Associated test now uses js-test.js, so modify expectations to include
js-test output (e.g. test description()).
* accessibility/mac/document-attributes.html:
Wait asynchronously for loading to complete ("AXLoaded: 1").
* accessibility/parent-delete.html:
Don't output all attributes, since it's not necessary for the purpose
of the test (which is to ensure calling allAttributes() in this
scenario doesn't cause a crash). Previously, the test output contained
loading AX attributes, so rather than making this test wait for
loading to complete I decided to remove the output all together.
* platform/mac/accessibility/parent-delete-expected.txt:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@288674 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 97c894a..1a56a46 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,23 @@
+2022-01-27  Tyler Wilcock  <tyler_w@apple.com>
+
+        AX ITM: Defer to the tree when determining AX object loading progress
+        https://bugs.webkit.org/show_bug.cgi?id=235646
+
+        Reviewed by Andres Gonzalez.
+
+        * accessibility/mac/document-attributes-expected.txt:
+        Associated test now uses js-test.js, so modify expectations to include
+        js-test output (e.g. test description()).
+        * accessibility/mac/document-attributes.html:
+        Wait asynchronously for loading to complete ("AXLoaded: 1").
+        * accessibility/parent-delete.html:
+        Don't output all attributes, since it's not necessary for the purpose
+        of the test (which is to ensure calling allAttributes() in this
+        scenario doesn't cause a crash). Previously, the test output contained
+        loading AX attributes, so rather than making this test wait for
+        loading to complete I decided to remove the output all together.
+        * platform/mac/accessibility/parent-delete-expected.txt:
+
 2022-01-27  Gabriel Nava Marino  <gnavamarino@apple.com>
 
         jsc_fuz/wktr: crash with new XRReferenceSpaceEvent('', {referenceSpace})
diff --git a/LayoutTests/accessibility/mac/document-attributes-expected.txt b/LayoutTests/accessibility/mac/document-attributes-expected.txt
index 9123b30..5e9cd69 100644
--- a/LayoutTests/accessibility/mac/document-attributes-expected.txt
+++ b/LayoutTests/accessibility/mac/document-attributes-expected.txt
@@ -1,9 +1,14 @@
+This test provides a base snapshot of the root web area's attributes.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
 AXHasDocumentRoleAncestor: 0
 AXHasWebApplicationAncestor: 0
 AXRole: AXWebArea
 AXRoleDescription: HTML content
-AXChildren: <array of size 0>
-AXChildrenInNavigationOrder: <array of size 0>
+AXChildren: <array of size 2>
+AXChildrenInNavigationOrder: <array of size 2>
 AXHelp:
 AXParent: <AXWebArea>
 AXSize: NSSize: {800, 600}
@@ -34,4 +39,7 @@
 AXWebSessionID: 2
 AXElementBusy: 0
 
+PASS successfullyParsed is true
+
+TEST COMPLETE
 
diff --git a/LayoutTests/accessibility/mac/document-attributes.html b/LayoutTests/accessibility/mac/document-attributes.html
index 6b22991..f60d8f2 100644
--- a/LayoutTests/accessibility/mac/document-attributes.html
+++ b/LayoutTests/accessibility/mac/document-attributes.html
@@ -1,24 +1,30 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
 <html>
 <head>
-<script>
-    if (window.testRunner)
-        testRunner.dumpAsText();
-
-    var log = function(msg)
-    {
-        document.getElementById("console").appendChild(document.createTextNode(msg + "\n"));
-    }
-
-    onload = function()
-    {
-        if (window.accessibilityController)
-            log(accessibilityController.focusedElement.allAttributes());
-        else
-            log("This test requires DumpRenderTree to run.")
-    }
-</script>
+<script src="../../resources/js-test.js"></script>
+<script src="../../resources/accessibility-helper.js"></script>
 </head>
 <body>
-<pre id="console"></pre>
+
+<script>
+    description("This test provides a base snapshot of the root web area's attributes.");
+
+    if (window.accessibilityController) {
+        window.jsTestIsAsync = true;
+
+        setTimeout(async function() {
+            let allAttributes;
+            await waitFor(() => {
+                if (!accessibilityController.focusedElement)
+                    return false;
+                allAttributes = accessibilityController.focusedElement.allAttributes();
+                // Wait for the page to be fully loaded.
+                return allAttributes.includes("AXLoaded: 1");
+            });
+            debugEscaped(allAttributes);
+            finishJSTest();
+        }, 0);
+    }
+</script>
 </body>
 </html>
diff --git a/LayoutTests/accessibility/parent-delete.html b/LayoutTests/accessibility/parent-delete.html
index 4d7c5e2..b9d5806 100644
--- a/LayoutTests/accessibility/parent-delete.html
+++ b/LayoutTests/accessibility/parent-delete.html
@@ -1,27 +1,22 @@
 <!DOCTYPE html>
 <html>
 <head>
+<script src="../resources/js-test.js"></script>
 <script>
-if (window.testRunner)
-    testRunner.dumpAsText();
-
 function runTest() {
-    var accessibilityElement;
-    {
-        var outer = document.getElementById("outer");
-        var inner = document.getElementById("inner");
-        var editable = document.getElementById("editable");
-        var result = document.getElementById("result");
-        editable.focus();
-        if (window.accessibilityController) {
-            var accessibilityElement = accessibilityController.focusedElement;
-        }
-        inner.removeChild(editable);
-        outer.removeChild(inner);
-    }
-    if (window.accessibilityController) {
-        result.innerText = accessibilityElement.allAttributes();
-    }
+    const outer = document.getElementById("outer");
+    const inner = document.getElementById("inner");
+    const editable = document.getElementById("editable");
+    const result = document.getElementById("result");
+    editable.focus();
+    let accessibilityElement;
+    if (window.accessibilityController)
+        accessibilityElement = accessibilityController.focusedElement;
+    inner.removeChild(editable);
+    outer.removeChild(inner);
+    // This should not cause a crash.
+    if (window.accessibilityController)
+        accessibilityElement.allAttributes();
 }
 </script>
 </head>
diff --git a/LayoutTests/platform/mac/accessibility/parent-delete-expected.txt b/LayoutTests/platform/mac/accessibility/parent-delete-expected.txt
index 8fc8a29..b0b9342 100644
--- a/LayoutTests/platform/mac/accessibility/parent-delete-expected.txt
+++ b/LayoutTests/platform/mac/accessibility/parent-delete-expected.txt
@@ -1,37 +1,5 @@
+PASS successfullyParsed is true
+
+TEST COMPLETE
 This test passes if there is no crash.
-AXHasDocumentRoleAncestor: 0
-AXHasWebApplicationAncestor: 0
-AXRole: AXWebArea
-AXRoleDescription: HTML content
-AXChildren: <array of size 1>
-AXChildrenInNavigationOrder: <array of size 1>
-AXHelp:
-AXParent: <AXWebArea>
-AXSize: NSSize: {800, 600}
-AXTitle:
-AXDescription:
-AXValue:
-AXFocused: 0
-AXEnabled: 1
-AXWindow: <AXWebArea>
-AXSelectedTextMarkerRange: (null)
-AXStartTextMarker: <AXWebArea>
-AXEndTextMarker: <AXWebArea>
-AXVisited: 0
-AXLinkedUIElements: <array of size 0>
-AXSelected: 0
-AXBlockQuoteLevel: 0
-AXTopLevelUIElement: <AXWebArea>
-AXLanguage:
-AXDOMIdentifier:
-AXDOMClassList: <array of size 0>
-AXLinkUIElements: <array of size 0>
-AXLoaded: 1
-AXLayoutCount: 2
-AXLoadingProgress: 1
-AXURL: LayoutTests/accessibility/parent-delete.html
-AXCaretBrowsingEnabled: 0
-AXPreventKeyboardDOMEventDispatch: 0
-AXWebSessionID: 2
-AXElementBusy: 0
 
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 276dba4..7d96218 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,73 @@
+2022-01-27  Tyler Wilcock  <tyler_w@apple.com>
+
+        AX ITM: Defer to the tree when determining AX object loading progress
+        https://bugs.webkit.org/show_bug.cgi?id=235646
+
+        Reviewed by Andres Gonzalez.
+
+        Currently, we set AXPropertyName::IsLoaded and AXPropertyName::EstimatedLoadingProgress
+        once in AXIsolatedObject::initializeAttributeData and never update it. This means that
+        if an object is created while the page is at some non-100% load percentage, AX clients
+        that request these attributes will always think the object isn't loaded.
+
+        This patch removes these two properties and instead defers to the isolated tree associated
+        with each object when determining the load percentage. Both of these properties were based
+        on the entire page / document load progress anyways, so it doesn't make sense to set the
+        same value for all of them.
+
+        We track loading progress by changing ProgressTracker to also notify
+        the page when progress changes, which in turn updates the associated
+        AXObjectCache with the new progress value.
+
+        Covered by accessibility/mac/document-attributes.html which verifies the AXLoad and
+        AXLoadingProgress attributes.
+
+        * accessibility/AXObjectCache.cpp:
+        (WebCore::AXObjectCache::AXObjectCache):
+        (WebCore::AXObjectCache::updateLoadingProgress):
+        Added.
+        * accessibility/AXObjectCache.h:
+        (WebCore::AXObjectCache::loadingFinished):
+        (WebCore::AXObjectCache::loadingProgress const):
+        Added.
+        * accessibility/AccessibilityObject.h:
+        * accessibility/AccessibilityObjectInterface.h:
+        * accessibility/AccessibilityRenderObject.cpp:
+        (WebCore::AccessibilityRenderObject::loadingProgress const):
+        (WebCore::AccessibilityRenderObject::estimatedLoadingProgress const):
+        Rename estimatedLoadingProgress methods to loadingProgress.
+        * accessibility/AccessibilityRenderObject.h:
+        * accessibility/isolatedtree/AXIsolatedObject.cpp:
+        (WebCore::AXIsolatedObject::initializeAttributeData):
+        * accessibility/isolatedtree/AXIsolatedObject.h:
+        * accessibility/isolatedtree/AXIsolatedTree.cpp:
+        (WebCore::AXIsolatedTree::create):
+        (WebCore::AXIsolatedTree::updateLoadingProgress):
+        (WebCore::AXIsolatedTree::applyPendingChanges):
+        Apply m_pendingEstimatedLoadingProgress.
+        * accessibility/isolatedtree/AXIsolatedTree.h:
+        (WebCore::AXIsolatedTree::loadingProgress):
+        Added.
+        * accessibility/mac/WebAccessibilityObjectWrapperMac.mm:
+        (-[WebAccessibilityObjectWrapper accessibilityAttributeValue:]):
+        Handle rename of estimatedLoadingProgress methods to loadingProgress.
+        * loader/ProgressTracker.cpp:
+        (WebCore::ProgressTracker::ProgressTracker):
+        Store a reference to the associated page in new m_page member.
+        (WebCore::ProgressTracker::progressStarted):
+        (WebCore::ProgressTracker::progressEstimateChanged):
+        (WebCore::ProgressTracker::finalProgressComplete):
+        (WebCore::ProgressTracker::incrementProgress):
+        Notify the Page when progress changes or completes.
+        * loader/ProgressTracker.h:
+        * page/Page.cpp:
+        (WebCore::Page::Page):
+        (WebCore::Page::progressEstimateChanged const):
+        Added.
+        (WebCore::Page::progressFinished const):
+        Added.
+        * page/Page.h:
+
 2022-01-27  Gabriel Nava Marino  <gnavamarino@apple.com>
 
         jsc_fuz/wktr: crash with new XRReferenceSpaceEvent('', {referenceSpace})
diff --git a/Source/WebCore/accessibility/AXObjectCache.cpp b/Source/WebCore/accessibility/AXObjectCache.cpp
index 124cd9e..4be27a4 100644
--- a/Source/WebCore/accessibility/AXObjectCache.cpp
+++ b/Source/WebCore/accessibility/AXObjectCache.cpp
@@ -89,6 +89,7 @@
 #include "InlineRunAndOffset.h"
 #include "MathMLElement.h"
 #include "Page.h"
+#include "ProgressTracker.h"
 #include "Range.h"
 #include "RenderAttachment.h"
 #include "RenderImage.h"
@@ -224,6 +225,13 @@
     , m_performCacheUpdateTimer(*this, &AXObjectCache::performCacheUpdateTimerFired)
 {
     ASSERT(isMainThread());
+
+    // If loading completed before the cache was created, loading progress will have been reset to zero.
+    // Consider loading progress to be 100% in this case.
+    double loadingProgress = document.page() ? document.page()->progress().estimatedProgress() : 1;
+    if (loadingProgress <= 0)
+        loadingProgress = 1;
+    m_loadingProgress = loadingProgress;
 }
 
 AXObjectCache::~AXObjectCache()
@@ -971,6 +979,22 @@
     get(node);
 }
 
+void AXObjectCache::updateLoadingProgress(double newProgressValue)
+{
+#if ENABLE(ACCESSIBILITY_ISOLATED_TREE)
+    ASSERT_WITH_MESSAGE(newProgressValue >= 0 && newProgressValue <= 1, "unexpected loading progress value: %f", newProgressValue);
+    if (m_pageID) {
+        // Sometimes the isolated tree hasn't been created by the time we get loading progress updates,
+        // so cache this value in the AXObjectCache too so we can give it to the tree upon creation.
+        m_loadingProgress = newProgressValue;
+        if (auto tree = AXIsolatedTree::treeForPageID(*m_pageID))
+            tree->updateLoadingProgress(newProgressValue);
+    }
+#else
+    UNUSED_PARAM(newProgressValue);
+#endif
+}
+
 void AXObjectCache::handleChildrenChanged(AccessibilityObject& object)
 {
     // Handle MenuLists and MenuListPopups as special cases.
diff --git a/Source/WebCore/accessibility/AXObjectCache.h b/Source/WebCore/accessibility/AXObjectCache.h
index 2980a1f..1ae5df9 100644
--- a/Source/WebCore/accessibility/AXObjectCache.h
+++ b/Source/WebCore/accessibility/AXObjectCache.h
@@ -187,6 +187,9 @@
     void checkedStateChanged(Node*);
     // Called when a node has just been attached, so we can make sure we have the right subclass of AccessibilityObject.
     void updateCacheAfterNodeIsAttached(Node*);
+    void updateLoadingProgress(double);
+    void loadingFinished() { updateLoadingProgress(1); }
+    double loadingProgress() const { return m_loadingProgress; }
 
     void deferFocusedUIElementChangeIfNeeded(Node* oldFocusedNode, Node* newFocusedNode);
     void deferModalChange(Element*);
@@ -523,6 +526,7 @@
     Vector<std::pair<Node*, Node*>> m_deferredFocusedNodeChange;
     bool m_isSynchronizingSelection { false };
     bool m_performingDeferredCacheUpdate { false };
+    double m_loadingProgress { 0 };
 
 #if USE(ATK)
     ListHashSet<RefPtr<AccessibilityObject>> m_deferredAttachedWrapperObjectList;
diff --git a/Source/WebCore/accessibility/AccessibilityObject.h b/Source/WebCore/accessibility/AccessibilityObject.h
index 1c07072..1a963a5 100644
--- a/Source/WebCore/accessibility/AccessibilityObject.h
+++ b/Source/WebCore/accessibility/AccessibilityObject.h
@@ -295,7 +295,7 @@
     AXCoreObject* selectedTabItem() override { return nullptr; }
     AXCoreObject* selectedListItem() override;
     int layoutCount() const override { return 0; }
-    double estimatedLoadingProgress() const override { return 0; }
+    double loadingProgress() const override { return 0; }
     WEBCORE_EXPORT static bool isARIAControl(AccessibilityRole);
     bool supportsCheckedState() const override;
     
diff --git a/Source/WebCore/accessibility/AccessibilityObjectInterface.h b/Source/WebCore/accessibility/AccessibilityObjectInterface.h
index c656803..204b2b9 100644
--- a/Source/WebCore/accessibility/AccessibilityObjectInterface.h
+++ b/Source/WebCore/accessibility/AccessibilityObjectInterface.h
@@ -1047,7 +1047,7 @@
     virtual AXCoreObject* selectedTabItem() = 0;
     virtual AXCoreObject* selectedListItem() = 0;
     virtual int layoutCount() const = 0;
-    virtual double estimatedLoadingProgress() const = 0;
+    virtual double loadingProgress() const = 0;
     virtual String brailleLabel() const = 0;
     virtual String brailleRoleDescription() const = 0;
     virtual String embeddedImageDescription() const = 0;
diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp
index 49886e5..efc15c6 100644
--- a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp
+++ b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp
@@ -1536,7 +1536,7 @@
     return m_renderer ? !m_renderer->document().parser() : false;
 }
 
-double AccessibilityRenderObject::estimatedLoadingProgress() const
+double AccessibilityRenderObject::loadingProgress() const
 {
     if (!m_renderer)
         return 0;
diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.h b/Source/WebCore/accessibility/AccessibilityRenderObject.h
index 595f0a6..90e3991 100644
--- a/Source/WebCore/accessibility/AccessibilityRenderObject.h
+++ b/Source/WebCore/accessibility/AccessibilityRenderObject.h
@@ -83,7 +83,7 @@
     AccessibilityObjectInclusion defaultObjectInclusion() const override;
     
     int layoutCount() const override;
-    double estimatedLoadingProgress() const override;
+    double loadingProgress() const override;
     
     AccessibilityObject* firstChild() const override;
     AccessibilityObject* lastChild() const override;
diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
index ecf818b..82c7c1d 100644
--- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
+++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.cpp
@@ -110,7 +110,6 @@
     setProperty(AXPropertyName::IsLink, object.isLink());
     setProperty(AXPropertyName::IsLinked, object.isLinked());
     setProperty(AXPropertyName::IsList, object.isList());
-    setProperty(AXPropertyName::IsLoaded, object.isLoaded());
     setProperty(AXPropertyName::IsMediaTimeline, object.isMediaTimeline());
     setProperty(AXPropertyName::IsMenu, object.isMenu());
     setProperty(AXPropertyName::IsMenuBar, object.isMenuBar());
@@ -171,7 +170,6 @@
     setObjectProperty(AXPropertyName::SelectedRadioButton, object.selectedRadioButton());
     setObjectProperty(AXPropertyName::SelectedTabItem, object.selectedTabItem());
     setProperty(AXPropertyName::LayoutCount, object.layoutCount());
-    setProperty(AXPropertyName::EstimatedLoadingProgress, object.estimatedLoadingProgress());
     setProperty(AXPropertyName::SupportsARIAOwns, object.supportsARIAOwns());
     setProperty(AXPropertyName::HasPopup, object.hasPopup());
     setProperty(AXPropertyName::PopupValue, object.popupValue().isolatedCopy());
diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.h b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.h
index c8efb2f..9800699 100644
--- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.h
+++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedObject.h
@@ -221,7 +221,7 @@
     AXCoreObject* selectedRadioButton() override { return objectAttributeValue(AXPropertyName::SelectedRadioButton); }
     AXCoreObject* selectedTabItem() override { return objectAttributeValue(AXPropertyName::SelectedTabItem); }
     int layoutCount() const override { return intAttributeValue(AXPropertyName::LayoutCount); }
-    double estimatedLoadingProgress() const override { return doubleAttributeValue(AXPropertyName::EstimatedLoadingProgress); }
+    double loadingProgress() const override { return tree()->loadingProgress(); }
     bool supportsARIAOwns() const override { return boolAttributeValue(AXPropertyName::SupportsARIAOwns); }
     bool isActiveDescendantOfFocusedContainer() const override { return boolAttributeValue(AXPropertyName::IsActiveDescendantOfFocusedContainer); }
     void ariaControlsElements(AccessibilityChildrenVector& children) const override { fillChildrenVectorForProperty(AXPropertyName::ARIAControlsElements, children); }
@@ -513,7 +513,7 @@
     bool isFigureElement() const override;
     bool isHovered() const override;
     bool isIndeterminate() const override;
-    bool isLoaded() const override { return boolAttributeValue(AXPropertyName::IsLoaded); }
+    bool isLoaded() const override { return loadingProgress() >= 1; }
     bool isOnScreen() const override;
     bool isOffScreen() const override;
     bool isPressed() const override;
diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp b/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp
index 03ad3df..d702c0a 100644
--- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp
+++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.cpp
@@ -118,6 +118,7 @@
     ASSERT(!treePageCache().contains(*pageID));
     treePageCache().set(*pageID, tree.copyRef());
     treeIDCache().set(tree->treeID(), tree.copyRef());
+    tree->updateLoadingProgress(axObjectCache->loadingProgress());
 
     return tree;
 }
@@ -439,6 +440,16 @@
     m_pendingPropertyChanges.append({ axID, propertyMap });
 }
 
+void AXIsolatedTree::updateLoadingProgress(double newProgressValue)
+{
+    AXTRACE("AXIsolatedTree::updateLoadingProgress");
+    AXLOG(makeString("Queueing loading progress update to ", newProgressValue, " for treeID ", treeID()));
+    ASSERT(isMainThread());
+
+    Locker locker { m_changeLogLock };
+    m_pendingLoadingProgress = newProgressValue;
+}
+
 void AXIsolatedTree::removeNode(AXID axID)
 {
     AXTRACE("AXIsolatedTree::removeNode");
@@ -484,6 +495,8 @@
 
     Locker locker { m_changeLogLock };
 
+    m_loadingProgress = m_pendingLoadingProgress;
+
     if (m_pendingFocusedNodeID != m_focusedNodeID) {
         AXLOG(makeString("focusedNodeID ", m_focusedNodeID.loggingString(), " pendingFocusedNodeID ", m_pendingFocusedNodeID.loggingString()));
 
diff --git a/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h b/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h
index 20a4d1c..09fe1de 100644
--- a/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h
+++ b/Source/WebCore/accessibility/isolatedtree/AXIsolatedTree.h
@@ -111,7 +111,6 @@
     EditableAncestor,
     ElementRect,
     EmbeddedImageDescription,
-    EstimatedLoadingProgress,
     ExpandedTextValue,
     FileUploadButtonReturnsValueInTitle,
     FocusableAncestor,
@@ -168,7 +167,6 @@
     IsLinked,
     IsList,
     IsListBox,
-    IsLoaded,
     IsMathElement,
     IsMathFraction,
     IsMathFenced,
@@ -361,6 +359,9 @@
     void updateSubtree(AXCoreObject&);
     void updateChildren(AXCoreObject&);
 
+    double loadingProgress() { return m_loadingProgress; }
+    void updateLoadingProgress(double);
+
     // Removes the given node leaving all descendants alone.
     void removeNode(AXID);
     // Removes the given node and all its descendants.
@@ -411,6 +412,8 @@
     Vector<std::pair<AXID, Vector<AXID>>> m_pendingChildrenUpdates WTF_GUARDED_BY_LOCK(m_changeLogLock);
     AXID m_pendingFocusedNodeID WTF_GUARDED_BY_LOCK(m_changeLogLock);
     AXID m_focusedNodeID;
+    double m_pendingLoadingProgress WTF_GUARDED_BY_LOCK(m_changeLogLock) { 0 };
+    double m_loadingProgress { 0 };
     Lock m_changeLogLock;
 
     bool m_creatingSubtree { false };
diff --git a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm b/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm
index 11c4b9d..ea0616e 100644
--- a/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm
+++ b/Source/WebCore/accessibility/mac/WebAccessibilityObjectWrapperMac.mm
@@ -2033,7 +2033,7 @@
         if ([attributeName isEqualToString:@"AXLayoutCount"])
             return @(backingObject->layoutCount());
         if ([attributeName isEqualToString:NSAccessibilityLoadingProgressAttribute])
-            return @(backingObject->estimatedLoadingProgress());
+            return @(backingObject->loadingProgress());
         if ([attributeName isEqualToString:NSAccessibilityPreventKeyboardDOMEventDispatchAttribute])
             return [NSNumber numberWithBool:backingObject->preventKeyboardDOMEventDispatch()];
         if ([attributeName isEqualToString:NSAccessibilityCaretBrowsingEnabledAttribute])
diff --git a/Source/WebCore/loader/ProgressTracker.cpp b/Source/WebCore/loader/ProgressTracker.cpp
index fa84c36..a2c6d72 100644
--- a/Source/WebCore/loader/ProgressTracker.cpp
+++ b/Source/WebCore/loader/ProgressTracker.cpp
@@ -75,8 +75,9 @@
     long long estimatedLength;
 };
 
-ProgressTracker::ProgressTracker(UniqueRef<ProgressTrackerClient>&& client)
-    : m_client(WTFMove(client))
+ProgressTracker::ProgressTracker(Page& page, UniqueRef<ProgressTrackerClient>&& client)
+    : m_page(page)
+    , m_client(WTFMove(client))
     , m_progressHeartbeatTimer(*this, &ProgressTracker::progressHeartbeatTimerFired)
 {
 }
@@ -129,6 +130,7 @@
         m_isMainLoad = isMainFrame || elapsedTimeSinceMainLoadComplete < subframePartOfMainLoadThreshold;
 
         m_client->progressStarted(*m_originatingProgressFrame);
+        m_page.progressEstimateChanged(*m_originatingProgressFrame);
     }
     m_numProgressTrackedFrames++;
 
@@ -138,6 +140,12 @@
     InspectorInstrumentation::frameStartedLoading(frame);
 }
 
+void ProgressTracker::progressEstimateChanged(Frame& frame)
+{
+    m_client->progressEstimateChanged(frame);
+    m_page.progressEstimateChanged(frame);
+}
+
 void ProgressTracker::progressCompleted(Frame& frame)
 {
     LOG(Progress, "Progress completed (%p) - frame %p(\"%s\"), value %f, tracked frames %d, originating frame %p", this, &frame, frame.tree().uniqueName().string().utf8().data(), m_progressValue, m_numProgressTrackedFrames, m_originatingProgressFrame.get());
@@ -166,7 +174,7 @@
     // with final progress value.
     if (!m_finalProgressChangedSent) {
         m_progressValue = 1;
-        m_client->progressEstimateChanged(*frame);
+        progressEstimateChanged(*frame);
     }
 
     reset();
@@ -176,6 +184,7 @@
 
     frame->loader().client().setMainFrameDocumentReady(true);
     m_client->progressFinished(*frame);
+    m_page.progressFinished(*frame);
     frame->loader().loadProgressingStatusChanged();
 
     InspectorInstrumentation::frameStoppedLoading(*frame);
@@ -254,7 +263,7 @@
             if (m_progressValue == 1)
                 m_finalProgressChangedSent = true;
 
-            m_client->progressEstimateChanged(*frame);
+            progressEstimateChanged(*frame);
 
             m_lastNotifiedProgressValue = m_progressValue;
             m_lastNotifiedProgressTime = now;
diff --git a/Source/WebCore/loader/ProgressTracker.h b/Source/WebCore/loader/ProgressTracker.h
index c0d21e6..b3aab2a 100644
--- a/Source/WebCore/loader/ProgressTracker.h
+++ b/Source/WebCore/loader/ProgressTracker.h
@@ -44,7 +44,7 @@
     WTF_MAKE_NONCOPYABLE(ProgressTracker);
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    explicit ProgressTracker(UniqueRef<ProgressTrackerClient>&&);
+    explicit ProgressTracker(Page&, UniqueRef<ProgressTrackerClient>&&);
     ~ProgressTracker();
 
     ProgressTrackerClient& client() { return m_client.get(); }
@@ -66,9 +66,11 @@
 private:
     void reset();
     void finalProgressComplete();
+    void progressEstimateChanged(Frame&);
 
     void progressHeartbeatTimerFired();
 
+    Page& m_page;
     UniqueRef<ProgressTrackerClient> m_client;
     RefPtr<Frame> m_originatingProgressFrame;
     HashMap<ResourceLoaderIdentifier, std::unique_ptr<ProgressItem>> m_progressItems;
diff --git a/Source/WebCore/page/Page.cpp b/Source/WebCore/page/Page.cpp
index c881b26..580da11 100644
--- a/Source/WebCore/page/Page.cpp
+++ b/Source/WebCore/page/Page.cpp
@@ -48,6 +48,7 @@
 #include "DiagnosticLoggingClient.h"
 #include "DiagnosticLoggingKeys.h"
 #include "DisplayRefreshMonitorManager.h"
+#include "DocumentInlines.h"
 #include "DocumentLoader.h"
 #include "DocumentMarkerController.h"
 #include "DocumentTimeline.h"
@@ -274,7 +275,7 @@
     , m_pointerLockController(makeUnique<PointerLockController>(*this))
 #endif
     , m_settings(Settings::create(this))
-    , m_progress(makeUnique<ProgressTracker>(WTFMove(pageConfiguration.progressTrackerClient)))
+    , m_progress(makeUnique<ProgressTracker>(*this, WTFMove(pageConfiguration.progressTrackerClient)))
     , m_backForwardController(makeUnique<BackForwardController>(*this, WTFMove(pageConfiguration.backForwardClient)))
     , m_mainFrame(Frame::create(this, nullptr, WTFMove(pageConfiguration.loaderClientForMainFrame)))
     , m_editorClient(WTFMove(pageConfiguration.editorClient))
@@ -583,6 +584,22 @@
 #endif
 }
 
+void Page::progressEstimateChanged(Frame& frameWithProgressUpdate) const
+{
+    if (auto* document = frameWithProgressUpdate.document()) {
+        if (auto* axObjectCache = document->existingAXObjectCache())
+            axObjectCache->updateLoadingProgress(progress().estimatedProgress());
+    }
+}
+
+void Page::progressFinished(Frame& frameWithCompletedProgress) const
+{
+    if (auto* document = frameWithCompletedProgress.document()) {
+        if (auto* axObjectCache = document->existingAXObjectCache())
+            axObjectCache->loadingFinished();
+    }
+}
+
 bool Page::openedByDOM() const
 {
     return m_openedByDOM;
diff --git a/Source/WebCore/page/Page.h b/Source/WebCore/page/Page.h
index be7f813..98209c1 100644
--- a/Source/WebCore/page/Page.h
+++ b/Source/WebCore/page/Page.h
@@ -361,6 +361,8 @@
 
     Settings& settings() const { return *m_settings; }
     ProgressTracker& progress() const { return *m_progress; }
+    void progressEstimateChanged(Frame&) const;
+    void progressFinished(Frame&) const;
     BackForwardController& backForward() const { return *m_backForwardController; }
 
     Seconds domTimerAlignmentInterval() const { return m_domTimerAlignmentInterval; }