Skip to content

Commit

Permalink
[macOS] Crash when inserting writing suggestions in an editable `disp…
Browse files Browse the repository at this point in the history
…lay: grid` container

https://bugs.webkit.org/show_bug.cgi?id=275426
rdar://129366300

Reviewed by Richard Robinson and Abrar Protyasha.

Make writing suggestions robust in the case where the process of attaching the suggestions renderer
to its parent in the render tree causes the parent to become destroyed. This can happen if, for
instance, the renderer is being inserted underneath an anonymous renderer inside of a grid
container like so:

```
<body>
    <div style="display: grid;" contenteditable></div>
</body>
```

...with render tree:

```
RenderBlock {HTML} at (0,0)
  RenderBody {BODY} at (8,8)
    RenderGrid {DIV} at (0,0)
      RenderBlock (anonymous) at (0,0)
        RenderText {#text} at (0,0) // <----- text before suggestion
```

As a result of inserting the renderer, `removeLeftoverAnonymousBlock()` will replace the parent
renderer, which currently leads to a crash since we hold a `CheckedPtr` to it on the stack. Instead,
gracefully handle this (and similar) scenarios by deploying `WeakPtr`, and clean up the writing
suggestions renderer and return early in the case where the parent is destroyed after insertion.

Test: editing/input/mac/inline-prediction-in-grid-container.html

* LayoutTests/editing/input/mac/inline-prediction-in-grid-container-expected.txt: Added.
* LayoutTests/editing/input/mac/inline-prediction-in-grid-container.html: Added.
* Source/WebCore/rendering/updating/RenderTreeUpdaterGeneratedContent.cpp:
(WebCore::RenderTreeUpdater::GeneratedContent::updateWritingSuggestionsRenderer):

Canonical link: https://commits.webkit.org/279986@main
  • Loading branch information
whsieh committed Jun 13, 2024
1 parent b803141 commit cf817ce
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PASS successfullyParsed is true

TEST COMPLETE
To whom it
This test passes if showing inline predictions when typing in the above container don't cause a crash.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html>
<head>
<script src="../../../resources/js-test.js"></script>
<script src="../../../resources/ui-helper.js"></script>
<style>
[contenteditable] {
width: 300px;
height: 100px;
border: solid 1px black;
display: grid;
}
</style>
</head>
<body>
<div contenteditable></div>
<p>This test passes if showing inline predictions when typing in the above container don't cause a crash.</p>
<script>
jsTestIsAsync = true;

addEventListener("load", async () => {
document.querySelector("[contenteditable]").focus();
document.execCommand("InsertText", true, "To whom it");

await UIHelper.setInlinePrediction("To whom it may concern", 10);
await UIHelper.ensurePresentationUpdate();

finishJSTest();
});
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,13 @@ void RenderTreeUpdater::GeneratedContent::updateWritingSuggestionsRenderer(Rende
return;
}

CheckedPtr nodeBeforeWritingSuggestionsTextRenderer = dynamicDowncast<RenderText>(nodeBeforeWritingSuggestions->renderer());
WeakPtr nodeBeforeWritingSuggestionsTextRenderer = dynamicDowncast<RenderText>(nodeBeforeWritingSuggestions->renderer());
if (!nodeBeforeWritingSuggestionsTextRenderer) {
destroyWritingSuggestionsIfNeeded();
return;
}

CheckedPtr parentForWritingSuggestions = nodeBeforeWritingSuggestionsTextRenderer->parent();
WeakPtr parentForWritingSuggestions = nodeBeforeWritingSuggestionsTextRenderer->parent();
if (!parentForWritingSuggestions) {
destroyWritingSuggestionsIfNeeded();
return;
Expand Down Expand Up @@ -351,14 +351,19 @@ void RenderTreeUpdater::GeneratedContent::updateWritingSuggestionsRenderer(Rende
auto newWritingSuggestionsRenderer = WebCore::createRenderer<RenderInline>(RenderObject::Type::Inline, renderer.document(), WTFMove(newStyle));
newWritingSuggestionsRenderer->initializeStyle();

CheckedPtr rendererAfterWritingSuggestions = nodeBeforeWritingSuggestionsTextRenderer->nextSibling();
WeakPtr rendererAfterWritingSuggestions = nodeBeforeWritingSuggestionsTextRenderer->nextSibling();

auto writingSuggestionsText = WebCore::createRenderer<RenderText>(RenderObject::Type::Text, renderer.document(), writingSuggestionData->content());
m_updater.m_builder.attach(*newWritingSuggestionsRenderer, WTFMove(writingSuggestionsText));

editor.setWritingSuggestionRenderer(*newWritingSuggestionsRenderer.get());
m_updater.m_builder.attach(*parentForWritingSuggestions, WTFMove(newWritingSuggestionsRenderer), rendererAfterWritingSuggestions.get());

if (!parentForWritingSuggestions) {
destroyWritingSuggestionsIfNeeded();
return;
}

auto* prefixNode = nodeBeforeWritingSuggestionsTextRenderer->textNode();
if (!prefixNode) {
ASSERT_NOT_REACHED();
Expand Down

0 comments on commit cf817ce

Please sign in to comment.