Bug 236750 - AX: AccessibilityScrollView should be resilient to destruction of underlying scroll view
Summary: AX: AccessibilityScrollView should be resilient to destruction of underlying ...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Accessibility (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Tyler Wilcock
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2022-02-16 21:04 PST by Tyler Wilcock
Modified: 2022-02-18 08:48 PST (History)
10 users (show)

See Also:


Attachments
Patch (13.01 KB, patch)
2022-02-16 21:35 PST, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Patch (13.33 KB, patch)
2022-02-17 12:00 PST, Tyler Wilcock
no flags Details | Formatted Diff | Diff
Minimal reproduction of issue (939 bytes, text/plain)
2022-02-17 18:51 PST, Tyler Wilcock
andresg_22: review+
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Tyler Wilcock 2022-02-16 21:04:21 PST
Sometimes, we can get into a state where we have a valid AccessibilityScrollView, backed by an iframe, with a destroyed WeakPtr<ScrollView> m_scrollView. When this happens, all of the content inside the iframe becomes inaccessible. We need to handle the case where an iframe changes the frame it holds (e.g. respect the current frame in HTMLFrameOwner::contentFrame, which can change).
Comment 1 Radar WebKit Bug Importer 2022-02-16 21:04:34 PST
<rdar://problem/89066221>
Comment 2 Tyler Wilcock 2022-02-16 21:35:10 PST
Created attachment 452311 [details]
Patch
Comment 3 chris fleizach 2022-02-16 21:52:37 PST
patch looks good. is there any way to test this?
Comment 4 Andres Gonzalez 2022-02-17 05:17:38 PST
(In reply to Tyler Wilcock from comment #2)
> Created attachment 452311 [details]
> Patch

--- a/Source/WebCore/accessibility/AccessibilityScrollView.cpp
+++ a/Source/WebCore/accessibility/AccessibilityScrollView.cpp

+AXObjectCache* AccessibilityScrollView::currentAxObjectCache() const
+{
+    if (auto* cache = axObjectCache())
+        return cache;
+
+    // It's possible that the document associated with `this` was destroyed (and thus axObjectCache() will return null),
+    // but also that `this` still represents a valid frame owner (e.g. iframe). Return the cache of the iframe in this case.
+    return m_frameOwnerElement ? m_frameOwnerElement->document().axObjectCache() : nullptr;
+}

Can we just override AXCoreObject::axObjectCache instead of adding a new method? 

The comment here only makes sense for the reader who knows the implementation of AccessibilityObject::axObjectCache. this could be written more succinctly and clearer like:

    auto* document = this->document();
    return document ? document->axObjectCache() :
        m_frameOwnerElement ? m_frameOwnerElement->document().axObjectCache() : nullptr;

Or perhaps better yet to override document(), and then you wouldn't need to override axObjectCache().

+Document* AccessibilityScrollView::contentDocument() const
+{
+    if (m_frameOwnerElement)
+        return m_frameOwnerElement->contentDocument();
+    return nullptr;
+}

do we need this method separate from document()? Or we can just override document()?

If we need this, it could be written in two lines with the ? :, which I think it is better.

+FrameView* AccessibilityScrollView::contentDocumentFrameView() const
+{
+    if (auto* document = contentDocument())
+        return document->view();
+    return nullptr;
+}

Can we override documentFrameView() instead?

@@ -157,7 +194,7 @@ AccessibilityScrollbar* AccessibilityScrollView::addChildScrollbar(Scrollbar* sc
     if (!scrollbar)
         return nullptr;

-    AXObjectCache* cache = axObjectCache();
+    AXObjectCache* cache = currentAxObjectCache();

No need for this if you override axObjectCache().


 AccessibilityObject* AccessibilityScrollView::webAreaObject() const
 {
-    if (!is<FrameView>(m_scrollView))
-        return nullptr;
+    auto* document = contentDocument();
+    if (is<FrameView>(m_scrollView))
+        document = downcast<FrameView>(*m_scrollView).frame().document();

-    Document* document = downcast<FrameView>(*m_scrollView).frame().document();
     if (!document || !document->hasLivingRenderTree())
         return nullptr;

-    if (AXObjectCache* cache = axObjectCache())
+    if (auto* cache = currentAxObjectCache())
         return cache->getOrCreate(document);
I think this can be simplified with the right overrides.

 AccessibilityObject* AccessibilityScrollView::parentObject() const
 {
-    if (!is<FrameView>(m_scrollView))
+    if (!is<FrameView>(m_scrollView)) {
+        if (m_frameOwnerElement) {
+            if (auto* cache = m_frameOwnerElement->document().axObjectCache())
+                return cache->getOrCreate(m_frameOwnerElement.get());
+        }
         return nullptr;
+    }

     AXObjectCache* cache = axObjectCache();
     if (!cache)

Can this be simplified with the right overrides? i.e., no need to get the cache in two different places.

 ScrollableArea* AccessibilityScrollView::getScrollableAreaIfScrollable() const
 {
-    return m_scrollView.get();
+    return currentScrollView();
 }

Can we inline this in the header like scrollView() for consistency?

--- a/Source/WebCore/accessibility/AccessibilityScrollView.h
+++ a/Source/WebCore/accessibility/AccessibilityScrollView.h

+    // Returns the document for m_frameOwnerElement's current `contentFrame` (if present).
+    Document* contentDocument() const;
+    FrameView* contentDocumentFrameView() const;
...
+    AXObjectCache* currentAxObjectCache() const;

Do we need these, or we can just override document() and documentFrameView()?
Comment 5 Tyler Wilcock 2022-02-17 12:00:08 PST
Created attachment 452400 [details]
Patch
Comment 6 Tyler Wilcock 2022-02-17 12:00:57 PST
Fixed all of your comments. Still trying to get a minimal test case reproducing the issue.
Comment 7 Andres Gonzalez 2022-02-17 12:55:12 PST
(In reply to Tyler Wilcock from comment #5)
> Created attachment 452400 [details]
> Patch

 bool AccessibilityScrollView::isAttachment() const
 {
-    return m_scrollView && m_scrollView->platformWidget();
+    if (auto* scrollView = currentScrollView())
+        return scrollView->platformWidget();
+    return false;

I personally prefer the two line version:

auto* scrollView = currentScrollView();
return scrollView ? scrollView->platformWidget() : false;

But not big deal.

 PlatformWidget AccessibilityScrollView::platformWidget() const
 {
-    return m_scrollView ? m_scrollView->platformWidget() : nullptr;
+    if (auto* scrollView = currentScrollView())
+        return scrollView->platformWidget();
+    return nullptr;
 }

Same here:

auto* scrollView = currentScrollView();
return scrollView ? scrollView->platformWidget() : nullptr;
Comment 8 Tyler Wilcock 2022-02-17 18:51:06 PST
Created attachment 452465 [details]
Minimal reproduction of issue
Comment 9 EWS 2022-02-18 08:48:52 PST
Committed r290130 (247474@main): <https://commits.webkit.org/247474@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 452400 [details].